From c18a7f7b643deb25b33d068c0480a8623925c123 Mon Sep 17 00:00:00 2001
From: lingdocs <71590811+lingdocs@users.noreply.github.com>
Date: Wed, 18 Aug 2021 15:54:00 +0400
Subject: [PATCH] more in repo
---
.firebaserc | 5 +
.github/workflows/deploy-functions.yml | 33 +
.github/workflows/functions-ci.yml | 44 +
.github/workflows/website-ci.yml | 32 +
.gitignore | 69 +
.npmrc | 2 +
LICENSE | 8 +
README.md | 165 +-
architecture-source.svg | 1886 +++
architecture.svg | 3186 ++++
firebase.json | 15 +
functions/.gitignore | 18 +
functions/import-wordlist.js | 84 +
functions/package-lock.json | 2153 +++
functions/package.json | 35 +
functions/src/generate-password.ts | 9 +
functions/src/index.ts | 232 +
functions/src/lib/userDbName.ts | 12 +
functions/src/publish.ts | 238 +
functions/src/submissions.ts | 155 +
functions/src/word-list-maker.test.ts | 27 +
functions/src/word-list-maker.ts | 124 +
functions/tsconfig.json | 16 +
netlify.toml | 4 +
public/index.html | 3 +
website/.gitignore | 23 +
website/.npmrc | 2 +
website/package.json | 97 +
website/public/_redirects | 1 +
website/public/favicon.ico | Bin 0 -> 103869 bytes
website/public/icons/favicon.ico | Bin 0 -> 103869 bytes
website/public/icons/icon.png | Bin 0 -> 14258 bytes
website/public/icons/icon128.png | Bin 0 -> 3625 bytes
website/public/icons/icon144.png | Bin 0 -> 3737 bytes
website/public/icons/icon168.png | Bin 0 -> 4313 bytes
website/public/icons/icon192.png | Bin 0 -> 5015 bytes
website/public/icons/icon48.png | Bin 0 -> 1274 bytes
website/public/icons/icon72.png | Bin 0 -> 1894 bytes
website/public/icons/icon96.png | Bin 0 -> 2504 bytes
website/public/icons/touch-icon128.png | Bin 0 -> 3091 bytes
website/public/icons/touch-icon152.png | Bin 0 -> 2720 bytes
website/public/icons/touch-icon167.png | Bin 0 -> 2938 bytes
website/public/icons/touch-icon180.png | Bin 0 -> 5324 bytes
website/public/icons/touch-icon57.png | Bin 0 -> 1290 bytes
website/public/icons/touch-icon76.png | Bin 0 -> 1842 bytes
website/public/index.html | 62 +
website/public/manifest.json | 40 +
website/public/robots.txt | 3 +
website/src/App.css | 385 +
website/src/App.test.tsx | 779 +
website/src/App.tsx | 570 +
website/src/Context.ts | 12 +
website/src/components/AudioPlayButton.tsx | 37 +
website/src/components/BottomNavItem.tsx | 41 +
.../components/DictionaryStatusDisplay.tsx | 31 +
website/src/components/Entry.tsx | 43 +
website/src/components/ExtraEntryInfo.tsx | 117 +
website/src/components/ImageEditor.tsx | 162 +
.../src/components/InflectionSearchResult.tsx | 56 +
website/src/components/LoadingElipses.css | 55 +
website/src/components/LoadingElipses.tsx | 5 +
website/src/components/ReviewScoreInput.tsx | 54 +
website/src/components/SearchBar.tsx | 92 +
website/src/components/WordlistImage.tsx | 40 +
website/src/components/WordlistWordEditor.tsx | 173 +
website/src/custom-bootstrap.scss | 36 +
website/src/index.tsx | 30 +
website/src/lib/__mocks__/pouch-dbs.ts | 31 +
.../src/lib/__mocks__/wordlist-database.ts | 56 +
website/src/lib/audio-tools.ts | 39 +
website/src/lib/backend-calls.ts | 56 +
website/src/lib/backend-types.ts | 108 +
website/src/lib/badges.ts | 11 +
website/src/lib/dictionary-core.test.ts | 243 +
website/src/lib/dictionary-core.ts | 232 +
website/src/lib/dictionary-mock-fillers.ts | 1397 ++
website/src/lib/dictionary.ts | 384 +
website/src/lib/filler-words.ts | 46 +
website/src/lib/firebase.ts | 30 +
.../lib/fuzzify-pashto/fuzzify-pashto.test.ts | 459 +
.../src/lib/fuzzify-pashto/fuzzify-pashto.ts | 150 +
website/src/lib/fuzzify-pashto/replacer.ts | 303 +
website/src/lib/get-word-id.test.ts | 8 +
website/src/lib/get-word-id.ts | 10 +
website/src/lib/hitBottom.ts | 12 +
website/src/lib/image-tools.ts | 147 +
website/src/lib/inflection-search-helpers.ts | 114 +
website/src/lib/is-pashto.test.ts | 6 +
website/src/lib/is-pashto.ts | 9 +
website/src/lib/level-management.ts | 14 +
website/src/lib/options-reducer.test.ts | 105 +
website/src/lib/options-reducer.ts | 98 +
website/src/lib/options-storage.test.ts | 48 +
website/src/lib/options-storage.ts | 34 +
website/src/lib/pouch-dbs.ts | 170 +
website/src/lib/sanitize-pashto.ts | 17 +
website/src/lib/search-all-inflections.ts | 79 +
website/src/lib/search-pile.test.ts | 55 +
website/src/lib/search-pile.ts | 192 +
website/src/lib/spaced-repetition.ts | 98 +
website/src/lib/string-to-hex.test.ts | 6 +
website/src/lib/string-to-hex.ts | 14 +
website/src/lib/submissions.ts | 59 +
website/src/lib/time-utils.ts | 38 +
website/src/lib/wee-bit-fuzzy.ts | 41 +
website/src/lib/wordlist-database.ts | 217 +
website/src/react-app-env.d.ts | 1 +
website/src/reportWebVitals.ts | 15 +
website/src/screens/About.tsx | 47 +
website/src/screens/Account.tsx | 404 +
website/src/screens/EntryEditor.tsx | 342 +
website/src/screens/IsolatedEntry.tsx | 227 +
website/src/screens/Options.tsx | 236 +
website/src/screens/Results.tsx | 234 +
website/src/screens/ReviewTasks.tsx | 63 +
website/src/screens/Wordlist.tsx | 451 +
website/src/service-worker.ts | 80 +
website/src/serviceWorkerRegistration.ts | 142 +
website/src/setupTests.ts | 12 +
website/src/types.d.ts | 157 +
website/tsconfig.json | 26 +
website/yarn.lock | 13388 ++++++++++++++++
122 files changed, 32461 insertions(+), 1 deletion(-)
create mode 100644 .firebaserc
create mode 100644 .github/workflows/deploy-functions.yml
create mode 100644 .github/workflows/functions-ci.yml
create mode 100644 .github/workflows/website-ci.yml
create mode 100644 .gitignore
create mode 100644 .npmrc
create mode 100644 LICENSE
create mode 100644 architecture-source.svg
create mode 100644 architecture.svg
create mode 100644 firebase.json
create mode 100644 functions/.gitignore
create mode 100644 functions/import-wordlist.js
create mode 100644 functions/package-lock.json
create mode 100644 functions/package.json
create mode 100644 functions/src/generate-password.ts
create mode 100644 functions/src/index.ts
create mode 100644 functions/src/lib/userDbName.ts
create mode 100644 functions/src/publish.ts
create mode 100644 functions/src/submissions.ts
create mode 100644 functions/src/word-list-maker.test.ts
create mode 100644 functions/src/word-list-maker.ts
create mode 100644 functions/tsconfig.json
create mode 100644 netlify.toml
create mode 100644 public/index.html
create mode 100644 website/.gitignore
create mode 100644 website/.npmrc
create mode 100644 website/package.json
create mode 100644 website/public/_redirects
create mode 100644 website/public/favicon.ico
create mode 100644 website/public/icons/favicon.ico
create mode 100644 website/public/icons/icon.png
create mode 100644 website/public/icons/icon128.png
create mode 100644 website/public/icons/icon144.png
create mode 100644 website/public/icons/icon168.png
create mode 100644 website/public/icons/icon192.png
create mode 100644 website/public/icons/icon48.png
create mode 100644 website/public/icons/icon72.png
create mode 100644 website/public/icons/icon96.png
create mode 100644 website/public/icons/touch-icon128.png
create mode 100644 website/public/icons/touch-icon152.png
create mode 100644 website/public/icons/touch-icon167.png
create mode 100644 website/public/icons/touch-icon180.png
create mode 100644 website/public/icons/touch-icon57.png
create mode 100644 website/public/icons/touch-icon76.png
create mode 100644 website/public/index.html
create mode 100644 website/public/manifest.json
create mode 100644 website/public/robots.txt
create mode 100644 website/src/App.css
create mode 100644 website/src/App.test.tsx
create mode 100644 website/src/App.tsx
create mode 100644 website/src/Context.ts
create mode 100644 website/src/components/AudioPlayButton.tsx
create mode 100644 website/src/components/BottomNavItem.tsx
create mode 100644 website/src/components/DictionaryStatusDisplay.tsx
create mode 100644 website/src/components/Entry.tsx
create mode 100644 website/src/components/ExtraEntryInfo.tsx
create mode 100644 website/src/components/ImageEditor.tsx
create mode 100644 website/src/components/InflectionSearchResult.tsx
create mode 100644 website/src/components/LoadingElipses.css
create mode 100644 website/src/components/LoadingElipses.tsx
create mode 100644 website/src/components/ReviewScoreInput.tsx
create mode 100644 website/src/components/SearchBar.tsx
create mode 100644 website/src/components/WordlistImage.tsx
create mode 100644 website/src/components/WordlistWordEditor.tsx
create mode 100644 website/src/custom-bootstrap.scss
create mode 100644 website/src/index.tsx
create mode 100644 website/src/lib/__mocks__/pouch-dbs.ts
create mode 100644 website/src/lib/__mocks__/wordlist-database.ts
create mode 100644 website/src/lib/audio-tools.ts
create mode 100644 website/src/lib/backend-calls.ts
create mode 100644 website/src/lib/backend-types.ts
create mode 100644 website/src/lib/badges.ts
create mode 100644 website/src/lib/dictionary-core.test.ts
create mode 100644 website/src/lib/dictionary-core.ts
create mode 100644 website/src/lib/dictionary-mock-fillers.ts
create mode 100644 website/src/lib/dictionary.ts
create mode 100644 website/src/lib/filler-words.ts
create mode 100644 website/src/lib/firebase.ts
create mode 100644 website/src/lib/fuzzify-pashto/fuzzify-pashto.test.ts
create mode 100644 website/src/lib/fuzzify-pashto/fuzzify-pashto.ts
create mode 100644 website/src/lib/fuzzify-pashto/replacer.ts
create mode 100644 website/src/lib/get-word-id.test.ts
create mode 100644 website/src/lib/get-word-id.ts
create mode 100644 website/src/lib/hitBottom.ts
create mode 100644 website/src/lib/image-tools.ts
create mode 100644 website/src/lib/inflection-search-helpers.ts
create mode 100644 website/src/lib/is-pashto.test.ts
create mode 100644 website/src/lib/is-pashto.ts
create mode 100644 website/src/lib/level-management.ts
create mode 100644 website/src/lib/options-reducer.test.ts
create mode 100644 website/src/lib/options-reducer.ts
create mode 100644 website/src/lib/options-storage.test.ts
create mode 100644 website/src/lib/options-storage.ts
create mode 100644 website/src/lib/pouch-dbs.ts
create mode 100644 website/src/lib/sanitize-pashto.ts
create mode 100644 website/src/lib/search-all-inflections.ts
create mode 100644 website/src/lib/search-pile.test.ts
create mode 100644 website/src/lib/search-pile.ts
create mode 100644 website/src/lib/spaced-repetition.ts
create mode 100644 website/src/lib/string-to-hex.test.ts
create mode 100644 website/src/lib/string-to-hex.ts
create mode 100644 website/src/lib/submissions.ts
create mode 100644 website/src/lib/time-utils.ts
create mode 100644 website/src/lib/wee-bit-fuzzy.ts
create mode 100644 website/src/lib/wordlist-database.ts
create mode 100644 website/src/react-app-env.d.ts
create mode 100644 website/src/reportWebVitals.ts
create mode 100644 website/src/screens/About.tsx
create mode 100644 website/src/screens/Account.tsx
create mode 100644 website/src/screens/EntryEditor.tsx
create mode 100644 website/src/screens/IsolatedEntry.tsx
create mode 100644 website/src/screens/Options.tsx
create mode 100644 website/src/screens/Results.tsx
create mode 100644 website/src/screens/ReviewTasks.tsx
create mode 100644 website/src/screens/Wordlist.tsx
create mode 100644 website/src/service-worker.ts
create mode 100644 website/src/serviceWorkerRegistration.ts
create mode 100644 website/src/setupTests.ts
create mode 100644 website/src/types.d.ts
create mode 100644 website/tsconfig.json
create mode 100644 website/yarn.lock
diff --git a/.firebaserc b/.firebaserc
new file mode 100644
index 0000000..b3d1d3d
--- /dev/null
+++ b/.firebaserc
@@ -0,0 +1,5 @@
+{
+ "projects": {
+ "default": "lingdocs"
+ }
+}
diff --git a/.github/workflows/deploy-functions.yml b/.github/workflows/deploy-functions.yml
new file mode 100644
index 0000000..fb87923
--- /dev/null
+++ b/.github/workflows/deploy-functions.yml
@@ -0,0 +1,33 @@
+name: Deploy Functions
+
+on:
+ push:
+ branches:
+ - master
+ paths:
+ - 'functions/**'
+ - '.github/workflows/deploy-functions.yml'
+
+ workflow_dispatch:
+
+jobs:
+ deploy-functions:
+ runs-on: ubuntu-latest
+ env:
+ LINGDOCS_NPM_TOKEN: ${{ secrets.LINGDOCS_NPM_TOKEN }}
+ FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: '12.x'
+ - run: npm install -g firebase-tools
+ - run: |
+ cp .npmrc functions
+ cd website
+ yarn install
+ cd ..
+ cd functions
+ npm install
+ - name: deploy functions
+ run: firebase deploy -f --token ${FIREBASE_TOKEN}
\ No newline at end of file
diff --git a/.github/workflows/functions-ci.yml b/.github/workflows/functions-ci.yml
new file mode 100644
index 0000000..7f515ce
--- /dev/null
+++ b/.github/workflows/functions-ci.yml
@@ -0,0 +1,44 @@
+name: Functions CI
+
+on:
+ push:
+ branches:
+ - '*'
+ pull_request:
+ - '*'
+ paths:
+ - 'functions/**'
+ - '.github/workflows/functions-ci.yml'
+
+ workflow_dispatch:
+
+jobs:
+ build-and-serve-functions:
+ runs-on: ubuntu-latest
+ env:
+ LINGDOCS_NPM_TOKEN: ${{ secrets.LINGDOCS_NPM_TOKEN }}
+ FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: '12.x'
+ - run: npm install -g firebase-tools
+ - name: build functions
+ run: |
+ cp .npmrc functions
+ cd website
+ yarn install
+ cd ..
+ cd functions
+ npm install
+ npm run build
+ - name: start up emulator once
+ run: |
+ cd functions
+ firebase functions:config:get --token ${FIREBASE_TOKEN} > .runtimeconfig.json
+ echo '#!/bin/bash' > empty.sh
+ chmod +x empty.sh
+ firebase emulators:exec ./empty.sh --only functions --token ${FIREBASE_TOKEN}
+ rm .runtimeconfig.json
+ rm empty.sh
\ No newline at end of file
diff --git a/.github/workflows/website-ci.yml b/.github/workflows/website-ci.yml
new file mode 100644
index 0000000..6e37f08
--- /dev/null
+++ b/.github/workflows/website-ci.yml
@@ -0,0 +1,32 @@
+name: Website CI
+
+on:
+ push:
+ branches: [ '*' ]
+ paths:
+ - 'website/**'
+ - '.github/workflows/website-ci.yml'
+ pull_request:
+ branches: [ '*' ]
+ paths:
+ - 'website/**'
+ - '.github/workflows/website-ci.yml'
+
+ workflow_dispatch:
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./website
+ env:
+ LINGDOCS_NPM_TOKEN: ${{ secrets.LINGDOCS_NPM_TOKEN }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: '12.x'
+ - run: yarn install
+ - run: yarn build
+ - run: yarn test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c04899d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,69 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+firebase-debug.log*
+firebase-debug.*.log*
+
+# Firebase cache
+.firebase/
+
+# Firebase config
+
+# Uncomment this if you'd like others to create their own Firebase project.
+# For a team working on the same Firebase project(s), it is recommended to leave
+# it commented so all members can deploy to the same project(s) in .firebaserc.
+# .firebaserc
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# Firebase functions config/env for running functions locally
+.runtimeconfig.json
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..b6e362c
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+@lingdocs:registry=https://npm.lingdocs.com
+//npm.lingdocs.com/:_authToken=${LINGDOCS_NPM_TOKEN}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..152ffce
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,8 @@
+The MIT License (MIT)
+Copyright © 2021 lingdocs.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index c122ed5..e3b48fd 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,164 @@
-new monorepo
\ No newline at end of file
+# LingDocs Dictionary Monorepo
+
+[![Netlify Status](https://api.netlify.com/api/v1/badges/65b633a2-f123-4fcd-91bc-5e6acda43256/deploy-status)](https://app.netlify.com/sites/lingdocs-dictionary/deploys)
+![Website CI](https://github.com/lingdocs/dictionary.lingdocs.com/actions/workflows/website-ci.yml/badge.svg)
+![Functions CI](https://github.com/lingdocs/dictionary.lingdocs.com/actions/workflows/functions-ci.yml/badge.svg)
+![Functions Deploy](https://github.com/lingdocs/dictionary.lingdocs.com/actions/workflows/deploy-functions.yml/badge.svg)
+
+## Contents
+
+This monorepo contains:
+ - `/dictionary-client` the frontend of the dictionary, a React SPA
+ - `/account` a backend authentication server
+ - `/functions` backend Firebase functions for use with the dictionary
+
+### Dictionary Client
+
+SPA Dictionary Frontend
+
+Use [Yarn](https://yarnpkg.com/).
+
+```sh
+cd website
+yarn install
+```
+
+#### Development
+
+```sh
+yarn start
+```
+
+### Account
+
+Backend authentication server build on express / passport
+
+#### Development
+
+Use [npm](https://www.npmjs.com/).
+
+```sh
+cd account
+npm install
+```
+
+### Functions
+
+Backend Firebase functions
+
+Use [npm](https://www.npmjs.com/).
+
+```sh
+cd functions
+npm install
+```
+
+#### Development
+
+```sh
+npm run serve
+```
+
+## Architecture
+
+![LingDocs Pashto Dictioanry App Architecture](./architecture.svg)
+
+### Source Layer
+
+#### GitHub Git Repo
+
+The monorepo contains both a `website` folder for the frontend PWA and a `functions` folder for the backend functions. Both parts are written in TypeScript and are tied together using the types found in the `@lingdocs/pashto-inflector` package used by both as well as the types found in `./website/src/lib/backend-types.ts`
+
+##### `./website` frontend
+
+The front-end website code in `./website` is made with `create-react-app` and written in typescript with `jest` testing. It is a SPA and PWA.
+
+The deployment is done automatically by netlify upon pushing to the `master` branch.
+
+##### `./functions` backend
+
+The backend code found in `./functions` and is written in TypeScript.
+
+It is compiled and deployed automatically by the repo's GitHub Actions to Firebase Cloud Functions upon pushing to the `master` branch.
+
+#### Google Sheets Dictionary Source
+
+The content of the dictionary is based on a Google Sheets documents containing rows with the information for each dictionary entry. This can be edited by an editor directly, or through the website frontend with editor priveledges.
+
+A cloud function in the backend compiles the dictionary into binary form (protobuf) then uploads it into a Google Cloud Storage bucket. The deployment is triggered from the website by an editor.
+
+### Backend Layer
+
+#### Firebase Functions
+
+Serverless functions are used in conjungtion with Firebase Authentication to:
+- check if a user has elevated priveledges
+- receive edits or suggestions for the dictionary
+- compile and publish the dictionary
+- create and clean up elevated users in the CouchDB database
+
+#### Account Server
+
+Deployed through a self-hosted actions runner.
+
+The runner is launched by this line in a crontab
+
+```
+@reboot ./actions-runner/run.sh
+```
+
+Process managed by pm2 using this `ecosystem.config.js`
+
+```
+module.exports = {
+ apps : [{
+ name : "account",
+ cwd : "./actions-runner/_work/lingdocs-main/lingdocs-main/account",
+ script: "npm",
+ args: "start",
+ env: {
+ NODE_ENVIRONMENT: "************",
+ LINGDOCS_EMAIL_HOST: "**************",
+ LINGDOCS_EMAIL_USER: "**************",
+ LINGDOCS_EMAIL_PASS: "*****************",
+ LINGDOCS_COUCHDB: "****************",
+ LINGDOCS_ACCOUNT_COOKIE_SECRET: "******************",
+ LINGDOCS_ACCOUNT_GOOGLE_CLIENT_SECRET: "******************",
+ LINGDOCS_ACCOUNT_TWITTER_CLIENT_SECRET: "******************",
+ LINGDOCS_ACCOUNT_GITHUB_CLIENT_SECRET: "******************",
+ LINGDOCS_ACCOUNT_RECAPTCHA_SECRET: "6LcVjAUcAAAAAPWUK-******************",
+ }
+ }]
+}
+```
+
+```sh
+pm2 start ecosystem.config.js
+pm2 save
+```
+
+#### CouchDB
+
+When a user upgrades their account level to `student` or `editor`:
+
+1. A doc in the `_users` db is created with their Firebase Authentication info, account level, and a password they can use for syncing their personal wordlistdb
+2. A user database is created (by the firebase functions - *not* by the couchdb_peruser) which they use to sync their personal wordlist.
+
+There is also a `review-tasks` database which is used to store all the review tasks for editors and syncs with the review tasks in the app for the editor(s).
+
+#### Google Cloud Storage
+
+Contains:
+
+- `dict` - the dictionary content in protobuf format
+- `dict-info` - information about the version of the currently available dictionary in protobuf format
+
+The website fetches `dict-info` and `dict` as needed to check for the latest dictionary version and download it into memory/`lokijs`
+
+### Frontend Layer
+
+#### PWA
+
+The frontend is a static-site PWA/SPA built with `create-react-app` (React/TypeScript) and deployed to Netlify.
+
+
diff --git a/architecture-source.svg b/architecture-source.svg
new file mode 100644
index 0000000..5e4f93e
--- /dev/null
+++ b/architecture-source.svg
@@ -0,0 +1,1886 @@
+
+
diff --git a/architecture.svg b/architecture.svg
new file mode 100644
index 0000000..1c908b3
--- /dev/null
+++ b/architecture.svg
@@ -0,0 +1,3186 @@
+
+
diff --git a/firebase.json b/firebase.json
new file mode 100644
index 0000000..8c8dcbe
--- /dev/null
+++ b/firebase.json
@@ -0,0 +1,15 @@
+{
+ "functions": {
+ "predeploy": "cp .npmrc functions && cat .npmrc | envsubst > functions/.npmrc && cd functions && npm --prefix \"$RESOURCE_DIR\" run build",
+ "postdeploy": "rm functions/.npmrc"
+ },
+ "hosting": {
+ "public": "public",
+ "rewrites": [
+ {
+ "source": "/authory",
+ "function": "authory"
+ }
+ ]
+ }
+}
diff --git a/functions/.gitignore b/functions/.gitignore
new file mode 100644
index 0000000..4f794ea
--- /dev/null
+++ b/functions/.gitignore
@@ -0,0 +1,18 @@
+# Debug
+ui-debug.log
+
+# Compiled JavaScript files
+lib/**/*.js
+lib/**/*.js.map
+
+# TypeScript v1 declaration files
+typings/
+
+# Node.js dependency directory
+node_modules/
+
+# File with private NPM token(s) inserted for deploying function
+.npmrc
+
+# Firebase functions config/env for running functions locally
+.runtimeconfig.json
diff --git a/functions/import-wordlist.js b/functions/import-wordlist.js
new file mode 100644
index 0000000..e150bc1
--- /dev/null
+++ b/functions/import-wordlist.js
@@ -0,0 +1,84 @@
+const nano = require("nano");
+const oldCouch = nano(process.env.OLD_WORDLIST_COUCHDB);
+const newCouch = nano(process.env.LINGDOCS_COUCHDB);
+const email = process.argv[2];
+const newEmail = process.argv[3];
+
+function stringToHex(str) {
+ const arr1 = [];
+ for (let n = 0, l = str.length; n < l; n ++) {
+ const hex = Number(str.charCodeAt(n)).toString(16);
+ arr1.push(hex);
+ }
+ return arr1.join('');
+}
+
+async function getOldWordList() {
+ const usersDb = oldCouch.use("_users");
+ const res = await usersDb.find({
+ selector: {
+ originalEmail: email,
+ },
+ });
+ const { name } = res.docs[0];
+ const tag = stringToHex(name);
+ const userDb = oldCouch.db.use(`userdb-${tag}`);
+ const { rows } = await userDb.list({ include_docs: true });
+ const allDocs = rows.map((row) => row.doc);
+ return allDocs
+}
+
+function convertWordList(list) {
+ const now = Date.now();
+ return list.map((item) => ({
+ _id: item._id,
+ warmup: "done",
+ supermemo: {
+ interval: 0,
+ repetition: 0,
+ efactor: 2.5
+ },
+ dueDate: now,
+ entry: { ...item.w },
+ notes: item.notes,
+ }));
+}
+
+async function uploadToNewDb(wordlist) {
+ const usersDb = newCouch.use("_users");
+ const res = await usersDb.find({
+ selector: {
+ email: newEmail || email,
+ },
+ });
+ const { name } = res.docs[0];
+ const tag = stringToHex(name);
+ const userDb = newCouch.db.use(`userdb-${tag}`);
+ await userDb.bulk({ docs: wordlist });
+}
+
+// async function updateWarmup() {
+// const usersDb = newCouch.use("_users");
+// const res = await usersDb.find({
+// selector: {
+// email: newEmail || email,
+// },
+// });
+// const { name } = res.docs[0];
+// const tag = stringToHex(name);
+// const userDb = newCouch.db.use(`userdb-${tag}`);
+// const { rows } = await userDb.list({ include_docs: true });
+// const allDocs = rows.map((row) => row.doc);
+// const updated = allDocs.map((d) => ({ ...d, warmup: "done" }));
+// await userDb.bulk({ docs: updated });
+// }
+
+async function main() {
+ const oldWordList = await getOldWordList();
+ const newWordList = convertWordList(oldWordList);
+ uploadToNewDb(newWordList)
+}
+
+main();
+
+
diff --git a/functions/package-lock.json b/functions/package-lock.json
new file mode 100644
index 0000000..9b7b716
--- /dev/null
+++ b/functions/package-lock.json
@@ -0,0 +1,2153 @@
+{
+ "name": "functions",
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
+ "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "@firebase/app-types": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz",
+ "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg=="
+ },
+ "@firebase/auth-interop-types": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz",
+ "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw=="
+ },
+ "@firebase/component": {
+ "version": "0.1.21",
+ "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz",
+ "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==",
+ "requires": {
+ "@firebase/util": "0.3.4",
+ "tslib": "^1.11.1"
+ }
+ },
+ "@firebase/database": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.3.tgz",
+ "integrity": "sha512-i29rr3kcPltIkA8La9M1lgsSxx9bfu5lCQ0T+tbJptZ3UpqpcL1NzCcZa24cJjiLgq3HQNPyLvUvCtcPSFDlRg==",
+ "requires": {
+ "@firebase/auth-interop-types": "0.1.5",
+ "@firebase/component": "0.1.21",
+ "@firebase/database-types": "0.6.1",
+ "@firebase/logger": "0.2.6",
+ "@firebase/util": "0.3.4",
+ "faye-websocket": "0.11.3",
+ "tslib": "^1.11.1"
+ }
+ },
+ "@firebase/database-types": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz",
+ "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==",
+ "requires": {
+ "@firebase/app-types": "0.6.1"
+ }
+ },
+ "@firebase/logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz",
+ "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw=="
+ },
+ "@firebase/util": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz",
+ "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==",
+ "requires": {
+ "tslib": "^1.11.1"
+ }
+ },
+ "@google-cloud/common": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.6.0.tgz",
+ "integrity": "sha512-aHIFTqJZmeTNO9md8XxV+ywuvXF3xBm5WNmgWeeCK+XN5X+kGW0WEX94wGwj+/MdOnrVf4dL2RvSIt9J5yJG6Q==",
+ "requires": {
+ "@google-cloud/projectify": "^2.0.0",
+ "@google-cloud/promisify": "^2.0.0",
+ "arrify": "^2.0.1",
+ "duplexify": "^4.1.1",
+ "ent": "^2.2.0",
+ "extend": "^3.0.2",
+ "google-auth-library": "^7.0.2",
+ "retry-request": "^4.1.1",
+ "teeny-request": "^7.0.0"
+ }
+ },
+ "@google-cloud/firestore": {
+ "version": "4.9.6",
+ "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.9.6.tgz",
+ "integrity": "sha512-GYFOInoirAQaVKEgZqVv/XH232faG/cfW80IWOz9RCZJHeITRj71O/TpVG/zrJoge+cu3bes+VJgBkxa2HR9Dg==",
+ "optional": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "functional-red-black-tree": "^1.0.1",
+ "google-gax": "^2.9.2",
+ "protobufjs": "^6.8.6"
+ }
+ },
+ "@google-cloud/paginator": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz",
+ "integrity": "sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==",
+ "requires": {
+ "arrify": "^2.0.0",
+ "extend": "^3.0.2"
+ }
+ },
+ "@google-cloud/projectify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz",
+ "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ=="
+ },
+ "@google-cloud/promisify": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz",
+ "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw=="
+ },
+ "@google-cloud/storage": {
+ "version": "5.8.1",
+ "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.8.1.tgz",
+ "integrity": "sha512-qP8gCJ2myyMN3JMJN12d82Oo8VBSDO8vO4/x56dtQZX9+WISqcagurntnJVyFX885tIOtS97bsyv8qR1xv6HMg==",
+ "requires": {
+ "@google-cloud/common": "^3.6.0",
+ "@google-cloud/paginator": "^3.0.0",
+ "@google-cloud/promisify": "^2.0.0",
+ "arrify": "^2.0.0",
+ "async-retry": "^1.3.1",
+ "compressible": "^2.0.12",
+ "date-and-time": "^0.14.2",
+ "duplexify": "^4.0.0",
+ "extend": "^3.0.2",
+ "gaxios": "^4.0.0",
+ "gcs-resumable-upload": "^3.1.3",
+ "get-stream": "^6.0.0",
+ "hash-stream-validation": "^0.2.2",
+ "mime": "^2.2.0",
+ "mime-types": "^2.0.8",
+ "onetime": "^5.1.0",
+ "p-limit": "^3.0.1",
+ "pumpify": "^2.0.0",
+ "snakeize": "^0.1.0",
+ "stream-events": "^1.0.1",
+ "xdg-basedir": "^4.0.0"
+ }
+ },
+ "@grpc/grpc-js": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.2.10.tgz",
+ "integrity": "sha512-wj6GkNiorWYaPiIZ767xImmw7avMMVUweTvPFg4mJWOxz2180DKwfuxhJJZ7rpc1+7D3mX/v8vJdxTuIo71Ieg==",
+ "optional": true,
+ "requires": {
+ "@types/node": ">=12.12.47",
+ "google-auth-library": "^6.1.1",
+ "semver": "^6.2.0"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "14.14.32",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.32.tgz",
+ "integrity": "sha512-/Ctrftx/zp4m8JOujM5ZhwzlWLx22nbQJiVqz8/zE15gOeEW+uly3FSX4fGFpcfEvFzXcMCJwq9lGVWgyARXhg==",
+ "optional": true
+ },
+ "google-auth-library": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz",
+ "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==",
+ "optional": true,
+ "requires": {
+ "arrify": "^2.0.0",
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "fast-text-encoding": "^1.0.0",
+ "gaxios": "^4.0.0",
+ "gcp-metadata": "^4.2.0",
+ "gtoken": "^5.0.4",
+ "jws": "^4.0.0",
+ "lru-cache": "^6.0.0"
+ }
+ }
+ }
+ },
+ "@grpc/proto-loader": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz",
+ "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==",
+ "optional": true,
+ "requires": {
+ "lodash.camelcase": "^4.3.0",
+ "protobufjs": "^6.8.6"
+ }
+ },
+ "@jest/types": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
+ "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^15.0.0",
+ "chalk": "^4.0.0"
+ }
+ },
+ "@lingdocs/pashto-inflector": {
+ "version": "0.9.0",
+ "resolved": "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-0.9.0.tgz",
+ "integrity": "sha512-kiWVshiMGp/eT0vPTheujD9hJrUXAqLKG48a6iqX8RBURlv5hjk9t+CymKvLGYDO0Zrog0kAYcSo/PqkV0UKIw==",
+ "requires": {
+ "classnames": "^2.2.6",
+ "pbf": "^3.2.1",
+ "rambda": "^6.7.0"
+ }
+ },
+ "@popperjs/core": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.1.tgz",
+ "integrity": "sha512-DvJbbn3dUgMxDnJLH+RZQPnXak1h4ZVYQ7CWiFWjQwBFkVajT4rfw2PdpHLTSTwxrYfnoEXkuBiwkDm6tPMQeA=="
+ },
+ "@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=",
+ "optional": true
+ },
+ "@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "optional": true
+ },
+ "@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "optional": true
+ },
+ "@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=",
+ "optional": true
+ },
+ "@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
+ "optional": true,
+ "requires": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=",
+ "optional": true
+ },
+ "@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=",
+ "optional": true
+ },
+ "@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=",
+ "optional": true
+ },
+ "@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=",
+ "optional": true
+ },
+ "@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=",
+ "optional": true
+ },
+ "@restart/context": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz",
+ "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q=="
+ },
+ "@restart/hooks": {
+ "version": "0.3.26",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.26.tgz",
+ "integrity": "sha512-7Hwk2ZMYm+JLWcb7R9qIXk1OoUg1Z+saKWqZXlrvFwT3w6UArVNWgxYOzf+PJoK9zZejp8okPAKTctthhXLt5g==",
+ "requires": {
+ "lodash": "^4.17.20",
+ "lodash-es": "^4.17.20"
+ }
+ },
+ "@tootallnate/once": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
+ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
+ },
+ "@types/body-parser": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
+ "requires": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "@types/classnames": {
+ "version": "2.2.11",
+ "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz",
+ "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw=="
+ },
+ "@types/connect": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz",
+ "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==",
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "@types/cors": {
+ "version": "2.8.10",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz",
+ "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="
+ },
+ "@types/express": {
+ "version": "4.17.3",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz",
+ "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==",
+ "requires": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "@types/express-serve-static-core": {
+ "version": "4.17.18",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz",
+ "integrity": "sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA==",
+ "requires": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*"
+ }
+ },
+ "@types/google-spreadsheet": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/google-spreadsheet/-/google-spreadsheet-3.0.2.tgz",
+ "integrity": "sha512-866PHvBTTEIsaRdMv/Ypz/KK3r2Bi9qwQ8WRF/h4My9lT7cJhVwTD06cTvNHHKO0qiDRjUP9TubAXY3PBJrN9w=="
+ },
+ "@types/invariant": {
+ "version": "2.2.34",
+ "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz",
+ "integrity": "sha512-lYUtmJ9BqUN688fGY1U1HZoWT1/Jrmgigx2loq4ZcJpICECm/Om3V314BxdzypO0u5PORKGMM6x0OXaljV1YFg=="
+ },
+ "@types/istanbul-lib-coverage": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
+ "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==",
+ "dev": true
+ },
+ "@types/istanbul-lib-report": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+ "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "@types/istanbul-reports": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
+ "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "@types/jest": {
+ "version": "26.0.20",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz",
+ "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==",
+ "dev": true,
+ "requires": {
+ "jest-diff": "^26.0.0",
+ "pretty-format": "^26.0.0"
+ }
+ },
+ "@types/lodash": {
+ "version": "4.14.168",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
+ "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==",
+ "dev": true
+ },
+ "@types/long": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
+ "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==",
+ "optional": true
+ },
+ "@types/mime": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
+ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
+ },
+ "@types/node": {
+ "version": "10.17.55",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.55.tgz",
+ "integrity": "sha512-koZJ89uLZufDvToeWO5BrC4CR4OUfHnUz2qoPs/daQH6qq3IN62QFxCTZ+bKaCE0xaoCAJYE4AXre8AbghCrhg=="
+ },
+ "@types/prop-types": {
+ "version": "15.7.3",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
+ },
+ "@types/qs": {
+ "version": "6.9.6",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz",
+ "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA=="
+ },
+ "@types/range-parser": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
+ "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
+ },
+ "@types/react": {
+ "version": "17.0.3",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.3.tgz",
+ "integrity": "sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==",
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@types/react-transition-group": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz",
+ "integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/scheduler": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
+ "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA=="
+ },
+ "@types/serve-static": {
+ "version": "1.13.9",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz",
+ "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==",
+ "requires": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "@types/tough-cookie": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz",
+ "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A=="
+ },
+ "@types/warning": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
+ "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI="
+ },
+ "@types/yargs": {
+ "version": "15.0.13",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
+ "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
+ "dev": true,
+ "requires": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "@types/yargs-parser": {
+ "version": "20.2.0",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz",
+ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==",
+ "dev": true
+ },
+ "abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "requires": {
+ "event-target-shim": "^5.0.0"
+ }
+ },
+ "accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "requires": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ }
+ },
+ "agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "requires": {
+ "debug": "4"
+ }
+ },
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+ },
+ "arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="
+ },
+ "async-retry": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz",
+ "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==",
+ "requires": {
+ "retry": "0.12.0"
+ }
+ },
+ "axios": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+ "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+ "requires": {
+ "follow-redirects": "^1.10.0"
+ }
+ },
+ "axios-cookiejar-support": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz",
+ "integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==",
+ "requires": {
+ "is-redirect": "^1.0.0",
+ "pify": "^5.0.0"
+ }
+ },
+ "base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+ },
+ "bignumber.js": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz",
+ "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA=="
+ },
+ "body-parser": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+ "requires": {
+ "bytes": "3.1.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "on-finished": "~2.3.0",
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+ },
+ "bytes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+ },
+ "call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "requires": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ }
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "classnames": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "requires": {
+ "mime-db": ">= 1.43.0 < 2"
+ }
+ },
+ "configstore": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
+ "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==",
+ "requires": {
+ "dot-prop": "^5.2.0",
+ "graceful-fs": "^4.1.2",
+ "make-dir": "^3.0.0",
+ "unique-string": "^2.0.0",
+ "write-file-atomic": "^3.0.0",
+ "xdg-basedir": "^4.0.0"
+ }
+ },
+ "content-disposition": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+ "requires": {
+ "safe-buffer": "5.1.2"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ }
+ }
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+ },
+ "cookie": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+ },
+ "cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "requires": {
+ "object-assign": "^4",
+ "vary": "^1"
+ }
+ },
+ "crypto-random-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
+ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="
+ },
+ "csstype": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz",
+ "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g=="
+ },
+ "date-and-time": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.2.tgz",
+ "integrity": "sha512-EFTCh9zRSEpGPmJaexg7HTuzZHh6cnJj1ui7IGCFNXzd2QdpsNh05Db5TF3xzJm30YN+A8/6xHSuRcQqoc3kFA=="
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+ },
+ "dicer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz",
+ "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==",
+ "requires": {
+ "streamsearch": "0.1.2"
+ }
+ },
+ "diff-sequences": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
+ "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
+ "dev": true
+ },
+ "dom-helpers": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
+ "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "dot-prop": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
+ "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
+ "requires": {
+ "is-obj": "^2.0.0"
+ }
+ },
+ "duplexify": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz",
+ "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==",
+ "requires": {
+ "end-of-stream": "^1.4.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+ },
+ "end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "ent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
+ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0="
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+ },
+ "event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
+ },
+ "express": {
+ "version": "4.17.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+ "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+ "requires": {
+ "accepts": "~1.3.7",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.19.0",
+ "content-disposition": "0.5.3",
+ "content-type": "~1.0.4",
+ "cookie": "0.4.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.1.2",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.5",
+ "qs": "6.7.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.1.2",
+ "send": "0.17.1",
+ "serve-static": "1.14.1",
+ "setprototypeof": "1.1.1",
+ "statuses": "~1.5.0",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "optional": true
+ },
+ "fast-text-encoding": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz",
+ "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig=="
+ },
+ "faye-websocket": {
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz",
+ "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==",
+ "requires": {
+ "websocket-driver": ">=0.5.1"
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "firebase-admin": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.5.0.tgz",
+ "integrity": "sha512-OPXFOTDcAE+NORpfhq7YMEDk+vFClBtjfpkrjm2JHRxb8DpMm+K3AcusonFPU/WOH4FhiVN9JHB0+NPE20S3gQ==",
+ "requires": {
+ "@firebase/database": "^0.8.1",
+ "@firebase/database-types": "^0.6.1",
+ "@google-cloud/firestore": "^4.5.0",
+ "@google-cloud/storage": "^5.3.0",
+ "@types/node": "^10.10.0",
+ "dicer": "^0.3.0",
+ "jsonwebtoken": "^8.5.1",
+ "node-forge": "^0.10.0"
+ }
+ },
+ "firebase-functions": {
+ "version": "3.13.2",
+ "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.13.2.tgz",
+ "integrity": "sha512-XHgAQZqA62awr4l9mNlJv6qnv5MkMkLuo+hafdW0T7IJj1PgrZtuIo5x+ib2npAcB0XhX5Sg0QR1hMYPAlfbaA==",
+ "requires": {
+ "@types/express": "4.17.3",
+ "cors": "^2.8.5",
+ "express": "^4.17.1",
+ "lodash": "^4.17.14"
+ }
+ },
+ "firebase-functions-test": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-0.2.3.tgz",
+ "integrity": "sha512-zYX0QTm53wCazuej7O0xqbHl90r/v1PTXt/hwa0jo1YF8nDM+iBKnLDlkIoW66MDd0R6aGg4BvKzTTdJpvigUA==",
+ "dev": true,
+ "requires": {
+ "@types/lodash": "^4.14.104",
+ "lodash": "^4.17.5"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
+ "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA=="
+ },
+ "forwarded": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "optional": true
+ },
+ "gaxios": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.2.0.tgz",
+ "integrity": "sha512-Ms7fNifGv0XVU+6eIyL9LB7RVESeML9+cMvkwGS70xyD6w2Z80wl6RiqiJ9k1KFlJCUTQqFFc8tXmPQfSKUe8g==",
+ "requires": {
+ "abort-controller": "^3.0.0",
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^5.0.0",
+ "is-stream": "^2.0.0",
+ "node-fetch": "^2.3.0"
+ }
+ },
+ "gcp-metadata": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz",
+ "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==",
+ "requires": {
+ "gaxios": "^4.0.0",
+ "json-bigint": "^1.0.0"
+ }
+ },
+ "gcs-resumable-upload": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.3.tgz",
+ "integrity": "sha512-LjVrv6YVH0XqBr/iBW0JgRA1ndxhK6zfEFFJR4im51QVTj/4sInOXimY2evDZuSZ75D3bHxTaQAdXRukMc1y+w==",
+ "requires": {
+ "abort-controller": "^3.0.0",
+ "configstore": "^5.0.0",
+ "extend": "^3.0.2",
+ "gaxios": "^4.0.0",
+ "google-auth-library": "^7.0.0",
+ "pumpify": "^2.0.0",
+ "stream-events": "^1.0.4"
+ }
+ },
+ "get-intrinsic": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
+ "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "requires": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "get-stream": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz",
+ "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg=="
+ },
+ "google-auth-library": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.0.2.tgz",
+ "integrity": "sha512-vjyNZR3pDLC0u7GHLfj+Hw9tGprrJwoMwkYGqURCXYITjCrP9HprOyxVV+KekdLgATtWGuDkQG2MTh0qpUPUgg==",
+ "requires": {
+ "arrify": "^2.0.0",
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "fast-text-encoding": "^1.0.0",
+ "gaxios": "^4.0.0",
+ "gcp-metadata": "^4.2.0",
+ "gtoken": "^5.0.4",
+ "jws": "^4.0.0",
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "google-gax": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.10.3.tgz",
+ "integrity": "sha512-jESs/ME9WgMzfGQKJDu9ea2mEKjznKByRL+5xb8mKfHlbUfS/LxNLNCg/35RgXwVXcNSCqkEY90z8wHxvgdd/Q==",
+ "optional": true,
+ "requires": {
+ "@grpc/grpc-js": "~1.2.0",
+ "@grpc/proto-loader": "^0.5.1",
+ "@types/long": "^4.0.0",
+ "abort-controller": "^3.0.0",
+ "duplexify": "^4.0.0",
+ "fast-text-encoding": "^1.0.3",
+ "google-auth-library": "^7.0.2",
+ "is-stream-ended": "^0.1.4",
+ "node-fetch": "^2.6.1",
+ "protobufjs": "^6.10.2",
+ "retry-request": "^4.0.0"
+ }
+ },
+ "google-p12-pem": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz",
+ "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==",
+ "requires": {
+ "node-forge": "^0.10.0"
+ }
+ },
+ "google-spreadsheet": {
+ "version": "3.1.15",
+ "resolved": "https://registry.npmjs.org/google-spreadsheet/-/google-spreadsheet-3.1.15.tgz",
+ "integrity": "sha512-S5477f3Gf3Mz6AXgCw7dbaYnzu5aHou1AX4sDqrGboQWnAytkxqJGKQiXN+zzRTTcYzSTJCe0g7KqCPZO9xiOw==",
+ "requires": {
+ "axios": "^0.21.1",
+ "google-auth-library": "^6.1.3",
+ "lodash": "^4.17.20"
+ },
+ "dependencies": {
+ "google-auth-library": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz",
+ "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==",
+ "requires": {
+ "arrify": "^2.0.0",
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "fast-text-encoding": "^1.0.0",
+ "gaxios": "^4.0.0",
+ "gcp-metadata": "^4.2.0",
+ "gtoken": "^5.0.4",
+ "jws": "^4.0.0",
+ "lru-cache": "^6.0.0"
+ }
+ }
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+ "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
+ },
+ "gtoken": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz",
+ "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==",
+ "requires": {
+ "gaxios": "^4.0.0",
+ "google-p12-pem": "^3.0.3",
+ "jws": "^4.0.0"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
+ },
+ "hash-stream-validation": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz",
+ "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ=="
+ },
+ "http-errors": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ }
+ }
+ },
+ "http-parser-js": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz",
+ "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg=="
+ },
+ "http-proxy-agent": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
+ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
+ "requires": {
+ "@tootallnate/once": "1",
+ "agent-base": "6",
+ "debug": "4"
+ }
+ },
+ "https-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
+ "requires": {
+ "agent-base": "6",
+ "debug": "4"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+ },
+ "is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
+ },
+ "is-redirect": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+ "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
+ },
+ "is-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
+ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
+ },
+ "is-stream-ended": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz",
+ "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==",
+ "optional": true
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "jest-diff": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz",
+ "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^26.6.2",
+ "jest-get-type": "^26.3.0",
+ "pretty-format": "^26.6.2"
+ }
+ },
+ "jest-get-type": {
+ "version": "26.3.0",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz",
+ "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "requires": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
+ "jsonwebtoken": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+ "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "requires": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^5.6.0"
+ },
+ "dependencies": {
+ "jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "requires": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "requires": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+ }
+ }
+ },
+ "jwa": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
+ "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
+ "requires": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "jws": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
+ "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
+ "requires": {
+ "jwa": "^2.0.0",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
+ "lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
+ "optional": true
+ },
+ "lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
+ },
+ "lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
+ },
+ "lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
+ },
+ "lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+ },
+ "lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+ },
+ "lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+ },
+ "long": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+ "optional": true
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "requires": {
+ "semver": "^6.0.0"
+ }
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+ },
+ "mime": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
+ "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg=="
+ },
+ "mime-db": {
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
+ "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ=="
+ },
+ "mime-types": {
+ "version": "2.1.29",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
+ "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
+ "requires": {
+ "mime-db": "1.46.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "nano": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/nano/-/nano-9.0.3.tgz",
+ "integrity": "sha512-NFI8+6q5ihnozH6qK+BJ+ilnPfZzBhlUswaFgqUvSp2EN5eJ2BMxbzkYiBsN+waa+N95FculCdbneDmzLWfXaQ==",
+ "requires": {
+ "@types/tough-cookie": "^4.0.0",
+ "axios": "^0.21.1",
+ "axios-cookiejar-support": "^1.0.1",
+ "qs": "^6.9.4",
+ "tough-cookie": "^4.0.0"
+ },
+ "dependencies": {
+ "qs": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.0.tgz",
+ "integrity": "sha512-yjACOWijC6L/kmPZZAsVBNY2zfHSIbpdpL977quseu56/8BZ2LoF5axK2bGhbzhVKt7V9xgWTtpyLbxwIoER0Q==",
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
+ }
+ }
+ },
+ "negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+ },
+ "node-fetch": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
+ },
+ "node-forge": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
+ "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "object-inspect": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw=="
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ }
+ },
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ },
+ "parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+ },
+ "pbf": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
+ "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
+ "requires": {
+ "ieee754": "^1.1.12",
+ "resolve-protobuf-schema": "^2.1.0"
+ }
+ },
+ "pify": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
+ "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA=="
+ },
+ "pretty-format": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
+ "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^26.6.2",
+ "ansi-regex": "^5.0.0",
+ "ansi-styles": "^4.0.0",
+ "react-is": "^17.0.1"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
+ "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==",
+ "dev": true
+ }
+ }
+ },
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "prop-types-extra": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
+ "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
+ "requires": {
+ "react-is": "^16.3.2",
+ "warning": "^4.0.0"
+ }
+ },
+ "protobufjs": {
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz",
+ "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==",
+ "optional": true,
+ "requires": {
+ "@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": "^4.0.0"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "13.13.45",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.45.tgz",
+ "integrity": "sha512-703YTEp8AwQeapI0PTXDOj+Bs/mtdV/k9VcTP7z/de+lx6XjFMKdB+JhKnK+6PZ5za7omgZ3V6qm/dNkMj/Zow==",
+ "optional": true
+ }
+ }
+ },
+ "protocol-buffers-schema": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.5.1.tgz",
+ "integrity": "sha512-YVCvdhxWNDP8/nJDyXLuM+UFsuPk4+1PB7WGPVDzm3HTHbzFLxQYeW2iZpS4mmnXrQJGBzt230t/BbEb7PrQaw=="
+ },
+ "proxy-addr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
+ "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
+ "requires": {
+ "forwarded": "~0.1.2",
+ "ipaddr.js": "1.9.1"
+ }
+ },
+ "psl": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz",
+ "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==",
+ "requires": {
+ "duplexify": "^4.1.1",
+ "inherits": "^2.0.3",
+ "pump": "^3.0.0"
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ },
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+ },
+ "rambda": {
+ "version": "6.8.2",
+ "resolved": "https://registry.npmjs.org/rambda/-/rambda-6.8.2.tgz",
+ "integrity": "sha512-fIVr6nuqfHfJTguthGsWF930DkNq/ENraeSpYBj1kYvO/ieacux7rj2NW27JwwFrllFJwXkLpGyFMz99EwCJ3Q=="
+ },
+ "range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+ },
+ "raw-body": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+ "requires": {
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ }
+ },
+ "react": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
+ "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "react-bootstrap": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.1.tgz",
+ "integrity": "sha512-jbJNGx9n4JvKgxlvT8DLKSeF3VcqnPJXS9LFdzoZusiZCCGoYecZ9qSCBH5n2A+kjmuura9JkvxI9l7HD+bIdQ==",
+ "requires": {
+ "@babel/runtime": "^7.4.2",
+ "@restart/context": "^2.1.4",
+ "@restart/hooks": "^0.3.21",
+ "@types/classnames": "^2.2.10",
+ "@types/invariant": "^2.2.33",
+ "@types/prop-types": "^15.7.3",
+ "@types/react": ">=16.9.35",
+ "@types/react-transition-group": "^4.4.0",
+ "@types/warning": "^3.0.0",
+ "classnames": "^2.2.6",
+ "dom-helpers": "^5.1.2",
+ "invariant": "^2.2.4",
+ "prop-types": "^15.7.2",
+ "prop-types-extra": "^1.1.0",
+ "react-overlays": "^5.0.0",
+ "react-transition-group": "^4.4.1",
+ "uncontrollable": "^7.0.0",
+ "warning": "^4.0.3"
+ }
+ },
+ "react-dom": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz",
+ "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "scheduler": "^0.20.1"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
+ "react-overlays": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.0.0.tgz",
+ "integrity": "sha512-TKbqfAv23TFtCJ2lzISdx76p97G/DP8Rp4TOFdqM9n8GTruVYgE3jX7Zgb8+w7YJ18slTVcDTQ1/tFzdCqjVhA==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "@popperjs/core": "^2.5.3",
+ "@restart/hooks": "^0.3.25",
+ "@types/warning": "^3.0.0",
+ "dom-helpers": "^5.2.0",
+ "prop-types": "^15.7.2",
+ "uncontrollable": "^7.0.0",
+ "warning": "^4.0.3"
+ }
+ },
+ "react-transition-group": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
+ "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ }
+ },
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+ },
+ "resolve-protobuf-schema": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
+ "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
+ "requires": {
+ "protocol-buffers-schema": "^3.3.1"
+ }
+ },
+ "retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
+ },
+ "retry-request": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz",
+ "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==",
+ "requires": {
+ "debug": "^4.1.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "scheduler": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz",
+ "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ },
+ "send": {
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "destroy": "~1.0.4",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "~1.7.2",
+ "mime": "1.6.0",
+ "ms": "2.1.1",
+ "on-finished": "~2.3.0",
+ "range-parser": "~1.2.1",
+ "statuses": "~1.5.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+ "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.17.1"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+ },
+ "side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "requires": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ }
+ },
+ "signal-exit": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
+ },
+ "snakeize": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz",
+ "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0="
+ },
+ "statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+ },
+ "stream-events": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
+ "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
+ "requires": {
+ "stubs": "^3.0.0"
+ }
+ },
+ "stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
+ },
+ "streamsearch": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
+ "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "stubs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
+ "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls="
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "teeny-request": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz",
+ "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==",
+ "requires": {
+ "http-proxy-agent": "^4.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "node-fetch": "^2.6.1",
+ "stream-events": "^1.0.5",
+ "uuid": "^8.0.0"
+ }
+ },
+ "toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+ },
+ "tough-cookie": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
+ "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
+ "requires": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.1.2"
+ }
+ },
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ },
+ "type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ }
+ },
+ "typedarray-to-buffer": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "requires": {
+ "is-typedarray": "^1.0.0"
+ }
+ },
+ "typescript": {
+ "version": "3.9.9",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz",
+ "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==",
+ "dev": true
+ },
+ "uncontrollable": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
+ "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
+ "requires": {
+ "@babel/runtime": "^7.6.3",
+ "@types/react": ">=16.9.11",
+ "invariant": "^2.2.4",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
+ "unique-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
+ "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
+ "requires": {
+ "crypto-random-string": "^2.0.0"
+ }
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+ },
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+ },
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "websocket-driver": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+ "requires": {
+ "http-parser-js": ">=0.5.1",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ }
+ },
+ "websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "write-file-atomic": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
+ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
+ "requires": {
+ "imurmurhash": "^0.1.4",
+ "is-typedarray": "^1.0.0",
+ "signal-exit": "^3.0.2",
+ "typedarray-to-buffer": "^3.1.5"
+ }
+ },
+ "xdg-basedir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
+ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q=="
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
+ }
+ }
+}
diff --git a/functions/package.json b/functions/package.json
new file mode 100644
index 0000000..cb23eb9
--- /dev/null
+++ b/functions/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "functions",
+ "scripts": {
+ "build": "tsc",
+ "serve": "npm run build && firebase emulators:start --only functions",
+ "shell": "npm run build && firebase functions:shell",
+ "start": "npm run shell",
+ "deploy": "firebase deploy --only functions",
+ "logs": "firebase functions:log"
+ },
+ "engines": {
+ "node": "12"
+ },
+ "main": "lib/functions/src/index.js",
+ "dependencies": {
+ "@google-cloud/storage": "^5.8.1",
+ "@lingdocs/pashto-inflector": "^0.9.0",
+ "@types/cors": "^2.8.10",
+ "@types/google-spreadsheet": "^3.0.2",
+ "cors": "^2.8.5",
+ "firebase-admin": "^9.2.0",
+ "firebase-functions": "^3.11.0",
+ "google-spreadsheet": "^3.1.15",
+ "nano": "^9.0.3",
+ "react": "^17.0.1",
+ "react-bootstrap": "^1.5.1",
+ "react-dom": "^17.0.1"
+ },
+ "devDependencies": {
+ "@types/jest": "^26.0.20",
+ "firebase-functions-test": "^0.2.0",
+ "typescript": "^3.8.0"
+ },
+ "private": true
+}
diff --git a/functions/src/generate-password.ts b/functions/src/generate-password.ts
new file mode 100644
index 0000000..8316696
--- /dev/null
+++ b/functions/src/generate-password.ts
@@ -0,0 +1,9 @@
+export default function generatePassword(): string {
+ function makeChunk(): string {
+ return Math.random().toString(36).slice(2)
+ }
+ const password = new Array(4).fill(0).reduce((acc: string): string => (
+ acc + makeChunk()
+ ), "");
+ return password;
+}
\ No newline at end of file
diff --git a/functions/src/index.ts b/functions/src/index.ts
new file mode 100644
index 0000000..ae63665
--- /dev/null
+++ b/functions/src/index.ts
@@ -0,0 +1,232 @@
+import * as functions from "firebase-functions";
+import publish from "./publish";
+import {
+ receiveSubmissions,
+} from "./submissions";
+import generatePassword from "./generate-password";
+import * as BT from "../../website/src/lib/backend-types"
+import cors from "cors";
+import * as admin from "firebase-admin";
+import { getUserDbName } from "./lib/userDbName";
+
+const nano = require("nano")(functions.config().couchdb.couchdb_url);
+const usersDb = nano.db.use("_users");
+
+admin.initializeApp();
+
+const validateFirebaseIdToken = async (req: any, res: any, next: any) => {
+ if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
+ !(req.cookies && req.cookies.__session)) {
+ res.status(403).send({ message: "Unauthorized" });
+ return;
+ }
+
+ let idToken;
+ if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
+ // Read the ID Token from the Authorization header.
+ idToken = req.headers.authorization.split('Bearer ')[1];
+ } else if(req.cookies) {
+ // Read the ID Token from cookie.
+ idToken = req.cookies.__session;
+ } else {
+ // No cookie
+ res.status(403).send({ message: "Unauthorized" });
+ return;
+ }
+
+ try {
+ const decodedIdToken = await admin.auth().verifyIdToken(idToken);
+ req.user = decodedIdToken;
+ next();
+ return;
+ } catch (error) {
+ console.error('Error while verifying Firebase ID token:', error);
+ res.status(403).send({ message: "Unauthorized" });
+ return;
+ }
+};
+
+const isEditor = async (req: any) => {
+ const uid = req.user.uid as string;
+ const couchDbUser = await getCouchDbUser(uid);
+ return !!couchDbUser && couchDbUser.level === "editor";
+}
+
+export const publishDictionary = functions
+ .region("europe-west1")
+ .runWith({
+ timeoutSeconds: 200,
+ memory: "2GB"
+ })
+ .https.onRequest((req, res) => {
+ return cors({ origin: true })(req, res, () => {
+ validateFirebaseIdToken(req, res, async () => {
+ try {
+ const response = await publish();
+ return res.send(response);
+ } catch (error) {
+ return res.status(500).send({
+ error: error.toString(),
+ });
+ }
+ });
+ });
+});
+
+// TODO: BETTER HANDLING OF EXPRESS MIDDLEWARE
+
+export const submissions = functions
+ .region("europe-west1")
+ .runWith({
+ timeoutSeconds: 30,
+ memory: "1GB"
+ })
+ .https.onRequest((req, res) => {
+ return cors({ origin: true })(req, res, () => {
+ validateFirebaseIdToken(req, res, async () => {
+ if (!Array.isArray(req.body)) {
+ res.status(400).send({
+ ok: false,
+ error: "invalid submission",
+ });
+ return;
+ }
+ const suggestions = req.body as BT.SubmissionsRequest;
+ // @ts-ignore
+ const uid = req.user.uid as string;
+ const editor = await isEditor(req);
+ try {
+ const response = await receiveSubmissions(suggestions, editor);
+ // TODO: WARN IF ANY OF THE EDITS DIDN'T HAPPEN
+ res.send(response);
+ return;
+ } catch (error) {
+ console.error(error);
+ return res.status(500).send({
+ error: error.toString(),
+ });
+ };
+ }).catch(console.error);
+ });
+});
+
+export const getUserInfo = functions.region("europe-west1").https.onRequest((req, res) => {
+ return cors({ origin: true })(req, res, () => {
+ validateFirebaseIdToken(req, res, async () => {
+ try {
+ // @ts-ignore
+ const uid = req.user.uid as string;
+ const user = await getCouchDbUser(uid);
+ if (!user) {
+ const noneFound: BT.GetUserInfoResponse = {
+ ok: true,
+ message: "no couchdb user found",
+ };
+ res.send(noneFound);
+ return;
+ }
+ const userFound: BT.GetUserInfoResponse = { ok: true, user };
+ res.send(userFound);
+ return;
+ } catch(error) {
+ console.error(error);
+ res.status(500).send({
+ ok: false,
+ error: error.message,
+ });
+ }
+ }).catch(console.error);
+ });
+});
+
+// export const cleanUpUser = functions
+// .region("europe-west1")
+// .auth.user().onDelete(async (user) => {
+// const couchDbUser = await getCouchDbUser(user.uid);
+// if (!couchDbUser) return;
+// await usersDb.destroy(
+// `org.couchdb.user:${user.uid}`,
+// couchDbUser._rev,
+// );
+// try {
+// await nano.db.destroy(getUserDbName(user.uid));
+// } catch (e) {
+// console.log("errored destroying", e);
+// };
+// });
+
+export const upgradeUser = functions.region("europe-west1").https.onRequest((req, res) => {
+ return cors({ origin: true })(req, res, () => {
+ validateFirebaseIdToken(req, res, async () => {
+ const password = (req.body.password || "") as string;
+ const studentPassword = functions.config().upgrades.student_password as string;
+ if (password.toLowerCase() !== studentPassword.toLowerCase()) {
+ const wrongPass: BT.UpgradeUserResponse = {
+ ok: false,
+ error: "incorrect password",
+ };
+ res.send(wrongPass);
+ return;
+ }
+ // @ts-ignore
+ const uid = req.user.uid;
+ const couchDbUser = await getCouchDbUser(uid);
+ if (couchDbUser) {
+ const alreadyUpgraded: BT.UpgradeUserResponse = {
+ ok: true,
+ message: "user already upgraded",
+ };
+ res.send(alreadyUpgraded);
+ return;
+ }
+ const user = await admin.auth().getUser(uid);
+ const userdbPassword = generatePassword();
+ const newCouchDbUser: BT.CouchDbUser = {
+ _id: `org.couchdb.user:${user.uid}`,
+ type: "user",
+ name: user.uid,
+ email: user.email || "",
+ providerData: user.providerData,
+ displayName: user.displayName || "",
+ roles: [],
+ password: userdbPassword,
+ level: "student",
+ userdbPassword,
+ };
+ await usersDb.insert(newCouchDbUser);
+ // create wordlist database for user
+ const userDbName = getUserDbName(user.uid);
+ await nano.db.create(userDbName);
+ const securityInfo = {
+ admins: {
+ names: [user.uid],
+ roles: ["_admin"]
+ },
+ members: {
+ names: [user.uid],
+ roles: ["_admin"],
+ },
+ };
+ const userDb = nano.db.use(userDbName);
+ await userDb.insert(securityInfo, "_security");
+ // TODO: SET THE USERDBPASSWORD TO BE userdbPassword;
+ const upgraded: BT.UpgradeUserResponse = {
+ ok: true,
+ message: "user upgraded to student",
+ };
+ res.send(upgraded);
+ }).catch(console.error);
+ });
+});
+
+async function getCouchDbUser(uid: string): Promise {
+ const user = await usersDb.find({
+ selector: {
+ name: uid,
+ }
+ });
+ if (!user.docs.length) {
+ return undefined;
+ }
+ return user.docs[0] as BT.CouchDbUser;
+}
\ No newline at end of file
diff --git a/functions/src/lib/userDbName.ts b/functions/src/lib/userDbName.ts
new file mode 100644
index 0000000..9800e08
--- /dev/null
+++ b/functions/src/lib/userDbName.ts
@@ -0,0 +1,12 @@
+function stringToHex(str: string) {
+ const arr1 = [];
+ for (let n = 0, l = str.length; n < l; n ++) {
+ const hex = Number(str.charCodeAt(n)).toString(16);
+ arr1.push(hex);
+ }
+ return arr1.join('');
+}
+
+export function getUserDbName(uid: string): string {
+ return `userdb-${stringToHex(uid)}`;
+}
\ No newline at end of file
diff --git a/functions/src/publish.ts b/functions/src/publish.ts
new file mode 100644
index 0000000..a6e4c4a
--- /dev/null
+++ b/functions/src/publish.ts
@@ -0,0 +1,238 @@
+import { GoogleSpreadsheet } from "google-spreadsheet";
+import * as functions from "firebase-functions";
+import {
+ Types as T,
+ dictionaryEntryBooleanFields,
+ dictionaryEntryNumberFields,
+ dictionaryEntryTextFields,
+ standardizePashto,
+ validateEntry,
+ writeDictionary,
+ writeDictionaryInfo,
+ simplifyPhonetics,
+} from "@lingdocs/pashto-inflector";
+// import {
+// getWordList,
+// } from "./word-list-maker";
+import {
+ PublishDictionaryResponse,
+} from "../../website/src/lib/backend-types";
+import { Storage } from "@google-cloud/storage";
+const storage = new Storage({
+ projectId: "lingdocs",
+});
+
+const title = "LingDocs Pashto Dictionary"
+const license = "Copyright © 2021 lingdocs.com All Rights Reserved - Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - https://creativecommons.org/licenses/by-nc-sa/4.0/";
+const bucketName = "lingdocs";
+const baseUrl = `https://storage.googleapis.com/${bucketName}/`;
+const dictionaryFilename = "dictionary";
+const dictionaryInfoFilename = "dictionary-info";
+// const hunspellAffFileFilename = "ps_AFF.aff";
+// const hunspellDicFileFilename = "ps_AFF.dic";
+const url = `${baseUrl}${dictionaryFilename}`;
+const infoUrl = `${baseUrl}${dictionaryInfoFilename}`;
+
+function standardizePhonetics(f: string): string {
+ return f.replace(/’/g, "'");
+}
+
+// TODO: Create a seperate function for publishing the Hunspell that can run after the publish function?
+// to keep the publish function time down
+
+export default async function(): Promise {
+ const entries = await getRawEntries();
+ const errors = checkForErrors(entries);
+ if (errors.length) {
+ return({ ok: false, errors });
+ }
+ const duplicate = findDuplicateTs(entries);
+ if (duplicate) {
+ return({
+ ok: false,
+ errors: [{
+ errors: [`${duplicate.ts} is a duplicate ts`],
+ ts: duplicate.ts,
+ p: duplicate.p,
+ f: duplicate.f,
+ e: duplicate.e,
+ erroneousFields: ["ts"],
+ }],
+ });
+ }
+ const dictionary: T.Dictionary = {
+ info: {
+ title,
+ license,
+ url,
+ infoUrl,
+ release: new Date().getTime(),
+ numberOfEntries: entries.length,
+ },
+ entries,
+ }
+ await uploadDictionaryToStorage(dictionary);
+ // TODO: make this async and run after publish response
+ // doHunspell(entries).catch(console.error);
+ return {
+ ok: true,
+ info: dictionary.info
+ };
+
+}
+
+// async function doHunspell(entries: T.DictionaryEntry[]) {
+// const wordlistResponse = getWordList(entries);
+// if (!wordlistResponse.ok) {
+// throw new Error(JSON.stringify(wordlistResponse.errors));
+// }
+// const hunspell = makeHunspell(wordlistResponse.wordlist);
+// await uploadHunspellToStorage(hunspell);
+// }
+
+async function getRawEntries(): Promise {
+ const doc = new GoogleSpreadsheet(
+ functions.config().sheet.id,
+ );
+ await doc.useServiceAccountAuth({
+ client_email: functions.config().serviceacct.email,
+ private_key: functions.config().serviceacct.key,
+ });
+ await doc.loadInfo();
+ const sheet = doc.sheetsByIndex[0];
+ const rows = await sheet.getRows();
+ const entries = makeEntries(rows);
+ return entries;
+}
+
+function makeEntries(rows: any[]): T.DictionaryEntry[] {
+ const entries: T.DictionaryEntry[] = rows.map((row, i): T.DictionaryEntry => {
+ const e: T.DictionaryEntry = {
+ i: 1,
+ ts: parseInt(row.ts),
+ p: row.p,
+ f: row.f,
+ g: simplifyPhonetics(row.f),
+ e: row.e,
+ };
+ dictionaryEntryNumberFields.forEach((field: T.DictionaryEntryNumberField) => {
+ if (row[field]) {
+ e[field] = parseInt(row[field]);
+ }
+ });
+ dictionaryEntryTextFields.forEach((field: T.DictionaryEntryTextField) => {
+ if (row[field]) {
+ const content = field.slice(-1) === "p" ? standardizePashto(row[field]).trim()
+ : field.slice(-1) === "f" ? standardizePhonetics(row[field]).trim()
+ : row[field].trim();
+ e[field] = content;
+ }
+ });
+ dictionaryEntryBooleanFields.forEach((field: T.DictionaryEntryBooleanField) => {
+ if (row[field]) {
+ e[field] = true;
+ }
+ });
+ return e;
+ });
+ // add alphabetical index
+ entries.sort((a, b) => a.p.localeCompare(b.p, "ps"));
+ const entriesLength = entries.length;
+ for (let i = 0; i < entriesLength; i++) {
+ entries[i].i = i;
+ }
+ return entries;
+}
+
+function checkForErrors(entries: T.DictionaryEntry[]): T.DictionaryEntryError[] {
+ return entries.reduce((errors: T.DictionaryEntryError[], entry: T.DictionaryEntry) => {
+ const response = validateEntry(entry);
+ if ("errors" in response && response.errors.length) {
+ return [...errors, response];
+ }
+ if ("checkComplement" in response) {
+ const complement = entries.find((e) => e.ts === entry.l);
+ if (!complement) {
+ const error: T.DictionaryEntryError = {
+ errors: ["complement link not found in dictonary"],
+ ts: entry.ts,
+ p: entry.p,
+ f: entry.f,
+ e: entry.e,
+ erroneousFields: ["l"],
+ };
+ return [...errors, error];
+ }
+ if (!complement.c?.includes("n.") && !complement.c?.includes("adj.") && !complement.c?.includes("adv.")) {
+ const error: T.DictionaryEntryError = {
+ errors: ["complement link to invalid complement"],
+ ts: entry.ts,
+ p: entry.p,
+ f: entry.f,
+ e: entry.e,
+ erroneousFields: ["l"],
+ };
+ return [...errors, error];
+ }
+ }
+ return errors;
+ }, []);
+}
+
+function findDuplicateTs(entries: T.DictionaryEntry[]): T.DictionaryEntry | undefined {
+ const tsSoFar = new Set();
+ // tslint:disable-next-line: prefer-for-of
+ for (let i = 0; i < entries.length; i++) {
+ const ts = entries[i].ts;
+ if (tsSoFar.has(ts)) {
+ return entries[i];
+ }
+ tsSoFar.add(ts);
+ }
+ return undefined;
+}
+
+async function upload(content: Buffer | string, filename: string) {
+ const isBuffer = typeof content !== "string";
+ const file = storage.bucket(bucketName).file(filename);
+ await file.save(content, {
+ gzip: isBuffer ? false : true,
+ predefinedAcl: "publicRead",
+ metadata: {
+ contentType: isBuffer
+ ? "application/octet-stream"
+ : filename.slice(-5) === ".json"
+ ? "application/json"
+ : "text/plain; charset=UTF-8",
+ cacheControl: "no-cache",
+ },
+ });
+}
+
+// async function uploadHunspellToStorage(wordlist: {
+// affContent: string,
+// dicContent: string,
+// }) {
+// await Promise.all([
+// upload(wordlist.affContent, hunspellAffFileFilename),
+// upload(wordlist.dicContent, hunspellDicFileFilename),
+// ]);
+// }
+
+async function uploadDictionaryToStorage(dictionary: T.Dictionary) {
+ const dictionaryBuffer = writeDictionary(dictionary);
+ const dictionaryInfoBuffer = writeDictionaryInfo(dictionary.info);
+ await Promise.all([
+ upload(JSON.stringify(dictionary), `${dictionaryFilename}.json`),
+ upload(JSON.stringify(dictionary.info), `${dictionaryInfoFilename}.json`),
+ upload(dictionaryBuffer as Buffer, dictionaryFilename),
+ upload(dictionaryInfoBuffer as Buffer, dictionaryInfoFilename),
+ ]);
+}
+
+// function makeHunspell(wordlist: string[]) {
+// return {
+// dicContent: wordlist.reduce((acc, word) => acc + word + "\n", wordlist.length + "\n"),
+// affContent: "SET UTF-8\nCOMPLEXPREFIXES\nIGNORE ۱۲۳۴۵۶۷۸۹۰-=ًٌٍَُِّْ؛:؟.،,،؟\n",
+// };
+// }
diff --git a/functions/src/submissions.ts b/functions/src/submissions.ts
new file mode 100644
index 0000000..73e56f6
--- /dev/null
+++ b/functions/src/submissions.ts
@@ -0,0 +1,155 @@
+import { GoogleSpreadsheet } from "google-spreadsheet";
+import {
+ dictionaryEntryTextFields,
+ dictionaryEntryBooleanFields,
+ dictionaryEntryNumberFields,
+} from "@lingdocs/pashto-inflector";
+import * as BT from "../../website/src/lib/backend-types";
+import * as functions from "firebase-functions";
+
+const fieldsForEdit = [
+ ...dictionaryEntryTextFields,
+ ...dictionaryEntryNumberFields,
+ ...dictionaryEntryBooleanFields,
+].filter(field => !(["ts", "i"].includes(field)));
+
+// TODO: PASS NANO INTO FUNCTIONu
+const nano = require("nano")(functions.config().couchdb.couchdb_url);
+const reviewTasksDb = nano.db.use("review-tasks");
+
+export async function receiveSubmissions(e: BT.SubmissionsRequest, editor: boolean): Promise {
+ const { edits, reviewTasks } = sortSubmissions(e);
+
+ // TODO: BETTER PROMISE MULTI-TASKING
+ // 1. Add review tasks to the couchdb
+ // 2. Edit dictionary entries
+ // 3. Add new dictionary entries
+
+
+ if (reviewTasks.length) {
+ const docs = reviewTasks.map((task) => ({
+ ...task,
+ _rev: undefined,
+ }));
+ await reviewTasksDb.bulk({ docs });
+ }
+
+ if (editor && edits.length) {
+
+ const doc = new GoogleSpreadsheet(
+ functions.config().sheet.id,
+ );
+ await doc.useServiceAccountAuth({
+ client_email: functions.config().serviceacct.email,
+ private_key: functions.config().serviceacct.key,
+ });
+ await doc.loadInfo();
+ const dictionarySheet = doc.sheetsByIndex[0];
+
+ const {
+ newEntries,
+ entryEdits,
+ entryDeletions,
+ } = sortEdits(edits);
+
+ if (entryEdits.length || entryDeletions.length) {
+ const dictRows = await dictionarySheet.getRows();
+ entryEdits.forEach(async ({entry}) => {
+ const i = dictRows.findIndex((r: any) => parseInt(r.ts) === entry.ts);
+ if (i === -1) {
+ console.error("Tried editing an entry with a ts that doesn't exist");
+ } else {
+ fieldsForEdit.forEach((field) => {
+ const toWrite = entry[field];
+ const existing = dictRows[i][field];
+ if (toWrite) {
+ // something to write
+ dictRows[i][field] = toWrite;
+ } else if (existing && !toWrite) {
+ // something to erase
+ dictRows[i][field] = "";
+ }
+ });
+ }
+ try {
+ await dictRows[i].save();
+ } catch (error) {
+ console.error("error saving edit to entry " + entry.ts);
+ console.error(error);
+ }
+ });
+ entryDeletions.forEach(async ({ ts }) => {
+ const i = dictRows.findIndex((r: any) => parseInt(r.ts) === ts);
+ if (i === -1) {
+ console.error("Tried deleting an entry with ats that doesn't exist")
+ }
+ try {
+ await dictRows[i].delete();
+ } catch (error) {
+ console.error("error deleting error " + ts);
+ console.error(error);
+ }
+ });
+ }
+
+ if (newEntries.length) {
+ newEntries.forEach((n) => {
+ const entry = { ...n.entry };
+ // @ts-ignore
+ delete entry.i; // i not used in dictionary spreadsheet; added while building it
+ // @ts-ignore
+ dictionarySheet.addRow(entry).catch(console.error);
+ });
+ }
+ }
+
+ return {
+ ok: true,
+ message: `received ${reviewTasks.length} review task(s), and ${edits.length} edit(s)`,
+ submissions: e,
+ };
+}
+
+type SortedSubmissions = {
+ edits: BT.Edit[],
+ reviewTasks: BT.ReviewTask[],
+};
+
+export function sortSubmissions(submissions: BT.Submission[]): SortedSubmissions {
+ const base: SortedSubmissions = {
+ edits: [],
+ reviewTasks: [],
+ };
+ return submissions.reduce((acc, s): SortedSubmissions => ({
+ ...acc,
+ ...(s.type === "edit suggestion" || s.type === "issue" || s.type === "entry suggestion") ? {
+ reviewTasks: [...acc.reviewTasks, s],
+ } : {
+ edits: [...acc.edits, s],
+ },
+ }), base);
+}
+
+type SortedEdits = {
+ entryEdits: BT.EntryEdit[],
+ newEntries: BT.NewEntry[],
+ entryDeletions: BT.EntryDeletion[],
+}
+
+export function sortEdits(edits: BT.Edit[]): SortedEdits {
+ const base: SortedEdits = {
+ entryEdits: [],
+ newEntries: [],
+ entryDeletions: [],
+ }
+ return edits.reduce((acc, edit): SortedEdits => ({
+ ...acc,
+ ...edit.type === "entry edit" ? {
+ entryEdits: [...acc.entryEdits, edit],
+ } : edit.type === "new entry" ? {
+ newEntries: [...acc.newEntries, edit],
+ } : edit.type === "entry deletion" ? {
+ entryDeletions: [...acc.entryDeletions, edit],
+ } : {},
+ }), base);
+}
diff --git a/functions/src/word-list-maker.test.ts b/functions/src/word-list-maker.test.ts
new file mode 100644
index 0000000..7edaef5
--- /dev/null
+++ b/functions/src/word-list-maker.test.ts
@@ -0,0 +1,27 @@
+import { getWordList } from "./word-list-maker";
+
+const entries = [
+ { "ts": 0, p:"???", f: "abc", e: "oeu", g: "coeuch", i: 0 },
+ {"ts":1581189430959,"p":"پېش","f":"pesh","e":"ahead, in front; earlier, first, before","c":"adv.","g":"pesh","i":2574},
+ {"i":4424,"g":"cherta","ts":1527812531,"p":"چېرته","f":"cherta","e":"where (also used for if, when)"},
+ {"i":5389,"g":"daase","ts":1527812321,"p":"داسې","f":"daase","e":"such, like this, like that, like","c":"adv."},
+];
+const expectedInflections = [
+ "پیش",
+ "پېش",
+ "چیرته",
+ "چېرته",
+ "داسي",
+ "داسې",
+];
+
+describe('Make Wordlist', () => {
+ it("should return all inflections that can be generated from given entries", () => {
+ const response = getWordList(entries);
+ expect(response.ok).toBe(true);
+ expect("wordlist" in response).toBe(true);
+ if ("wordlist" in response) {
+ expect(response.wordlist).toEqual(expectedInflections);
+ }
+ });
+});
diff --git a/functions/src/word-list-maker.ts b/functions/src/word-list-maker.ts
new file mode 100644
index 0000000..7cb261f
--- /dev/null
+++ b/functions/src/word-list-maker.ts
@@ -0,0 +1,124 @@
+import {
+ inflectWord,
+ conjugateVerb,
+ Types as T,
+ pashtoConsonants,
+ isNounAdjOrVerb,
+} from "@lingdocs/pashto-inflector";
+
+
+function search(key: string, object: any): string[] {
+ // adapted from
+ // https://www.mikedoesweb.com/2016/es6-depth-first-object-tree-search/
+ function inside(needle: string, haystack: any, found: Set = new Set()): Set {
+ if (haystack === null) {
+ return found;
+ }
+ Object.keys(haystack).forEach((key: string) => {
+ if(key === needle && typeof haystack[key] === "string") {
+ haystack[key].split(" ").forEach((word: string) => {
+ found.add(word);
+ });
+ return;
+ }
+ if(typeof haystack[key] === 'object') {
+ inside(needle, haystack[key], found);
+ }
+ return;
+ });
+ return found;
+ };
+ return Array.from(inside(key, object));
+}
+
+export function getWordList(entries: T.DictionaryEntry[]): {
+ ok: true,
+ wordlist: string[],
+} | {
+ ok: false,
+ errors: T.DictionaryEntryError[],
+} {
+ const allInflections: Set = new Set();
+ const errors: T.DictionaryEntryError[] = [];
+ function getNounAdjInflections(entry: T.DictionaryEntry) {
+ if (entry.app) allInflections.add(entry.app);
+ if (entry.ppp) allInflections.add(entry.ppp);
+
+ const inflections = inflectWord(entry);
+ const wordsFromInf = inflections
+ ? search("p", inflections)
+ : [];
+ wordsFromInf.forEach(w => allInflections.add(w));
+ }
+ function getVerbConjugations(word: T.DictionaryEntry, linked?: T.DictionaryEntry) {
+ const pWords = search("p", conjugateVerb(word, linked));
+ pWords.forEach(w => allInflections.add(w));
+ }
+ // got the entries, make a wordList of all the possible inflections
+ entries.forEach((entry) => {
+ try {
+ if (entry.c && isNounAdjOrVerb(entry) === "nounAdj") {
+ // it's a noun/adjective - get all inflections and plurals etc.
+ getNounAdjInflections(entry);
+ // hack to add some plurals and mayonnaise
+ if (entry.c.includes("n. m.") && pashtoConsonants.includes(entry.p.slice(-1))) {
+ allInflections.add(entry.p + "ونه")
+ allInflections.add(entry.p + "ونو")
+ allInflections.add(entry.p + "ه");
+ }
+ if (entry.c.includes("n. f.") && entry.p.slice(-1) === "ا") {
+ allInflections.add(entry.p + "ګانې")
+ allInflections.add(entry.p + "ګانو");
+ }
+ } else if (entry.c && isNounAdjOrVerb(entry) === "verb") {
+ // it's a verb - get all the conjugations for it
+ if (entry.l && entry.c.includes("comp.")) {
+ // it's a compound verb, conjugate it with the linked complement
+ const linkedEntry = entries.find((e) => e.ts === entry.l);
+ getVerbConjugations(entry, linkedEntry);
+ } else {
+ // it's a non-compound verb, conjugate it
+ getVerbConjugations(entry);
+ }
+ } else {
+ // it's something else, just put the word(s) in
+ entry.p.split(" ").forEach(w => allInflections.add(w));
+ }
+ } catch (error) {
+ errors.push({
+ ts: entry.ts,
+ p: entry.p,
+ f: entry.f,
+ e: entry.e,
+ erroneousFields: [],
+ errors: ["error inflecting/conjugating entry", error.toString()],
+ });
+ }
+ });
+ if (errors.length) {
+ return ({
+ ok: false,
+ errors,
+ });
+ }
+
+ // add ی version of words with ې (to accomadate for some bad spelling)
+ allInflections.forEach((word: string) => {
+ // for words with ې in the middle, also have a version with ی in the middle instead
+ if (eInMiddleRegex.test(word)) {
+ allInflections.add(word.replace(eInMiddleRegex, "ی"));
+ }
+ // for words ending in ې, also have a version ending in ي
+ if (word.slice(-1) === "ې") {
+ allInflections.add(word.slice(0, -1) + "ي");
+ }
+ });
+ const wordlist = Array.from(allInflections).filter((s) => !(s.includes(".") || s.includes("?")));
+ wordlist.sort((a, b) => a.localeCompare(b, "ps"));
+ return {
+ ok: true,
+ wordlist,
+ };
+}
+
+const eInMiddleRegex = new RegExp("ې(?=[\u0621-\u065f\u0670-\u06d3\u06d5])", "g");
diff --git a/functions/tsconfig.json b/functions/tsconfig.json
new file mode 100644
index 0000000..8fb6498
--- /dev/null
+++ b/functions/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "noImplicitReturns": true,
+ "noUnusedLocals": true,
+ "outDir": "lib",
+ "sourceMap": true,
+ "strict": true,
+ "target": "es2017",
+ "esModuleInterop": true
+ },
+ "compileOnSave": true,
+ "include": [
+ "src"
+ ]
+}
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..281388f
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,4 @@
+[build]
+ base = "website"
+ command = "export REACT_APP_BUILD_NO=`git rev-parse --short HEAD` && yarn test && yarn build"
+ publish = "build"
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..b70e921
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,3 @@
+
+ Hello World
+
\ No newline at end of file
diff --git a/website/.gitignore b/website/.gitignore
new file mode 100644
index 0000000..4d29575
--- /dev/null
+++ b/website/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/website/.npmrc b/website/.npmrc
new file mode 100644
index 0000000..b6e362c
--- /dev/null
+++ b/website/.npmrc
@@ -0,0 +1,2 @@
+@lingdocs:registry=https://npm.lingdocs.com
+//npm.lingdocs.com/:_authToken=${LINGDOCS_NPM_TOKEN}
diff --git a/website/package.json b/website/package.json
new file mode 100644
index 0000000..bff63e0
--- /dev/null
+++ b/website/package.json
@@ -0,0 +1,97 @@
+{
+ "name": "pashto-dictionary-website",
+ "version": "0.1.0",
+ "license": "MIT",
+ "author": "lingdocs.com",
+ "private": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-free": "^5.15.2",
+ "@lingdocs/pashto-inflector": "^0.9.0",
+ "@testing-library/jest-dom": "^5.11.4",
+ "@testing-library/react": "^11.1.0",
+ "@testing-library/user-event": "^12.1.10",
+ "@types/jest": "^26.0.20",
+ "@types/node": "^14.14.33",
+ "@types/react": "^17.0.3",
+ "@types/react-dom": "^17.0.2",
+ "bootstrap": "^4.6.0",
+ "classnames": "^2.2.6",
+ "cron": "^1.8.2",
+ "dayjs": "^1.10.4",
+ "firebase": "^8.3.0",
+ "lokijs": "^1.5.11",
+ "mousetrap": "^1.6.5",
+ "node-sass": "^5.0.0",
+ "papaparse": "^5.3.0",
+ "pbf": "^3.2.1",
+ "pouchdb": "^7.2.2",
+ "react": "^17.0.1",
+ "react-bootstrap": "^1.5.1",
+ "react-dom": "^17.0.1",
+ "react-dropzone": "^11.3.2",
+ "react-firebaseui": "^4.1.0",
+ "react-ga": "^3.3.0",
+ "react-helmet": "^6.1.0",
+ "react-image-crop": "^8.6.9",
+ "react-image-file-resizer": "^0.4.4",
+ "react-router-dom": "^5.2.0",
+ "react-scripts": "4.0.3",
+ "relevancy": "^0.2.0",
+ "supermemo": "^2.0.17",
+ "typescript": "^4.2.3",
+ "web-vitals": "^0.2.4",
+ "workbox-background-sync": "^5.1.3",
+ "workbox-broadcast-update": "^5.1.3",
+ "workbox-cacheable-response": "^5.1.3",
+ "workbox-core": "^5.1.3",
+ "workbox-expiration": "^5.1.3",
+ "workbox-google-analytics": "^5.1.3",
+ "workbox-navigation-preload": "^5.1.3",
+ "workbox-precaching": "^5.1.3",
+ "workbox-range-requests": "^5.1.3",
+ "workbox-routing": "^5.1.3",
+ "workbox-strategies": "^5.1.3",
+ "workbox-streams": "^5.1.3"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject",
+ "test-ci": "yarn test --watchAll=false"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@types/cron": "^1.7.2",
+ "@types/history": "^4.7.8",
+ "@types/lokijs": "^1.5.3",
+ "@types/mousetrap": "^1.6.8",
+ "@types/papaparse": "^5.2.5",
+ "@types/pouchdb": "^6.4.0",
+ "@types/react-canvas-draw": "^1.1.0",
+ "@types/react-helmet": "^6.1.0",
+ "@types/react-image-crop": "^8.1.2",
+ "@types/react-router-dom": "^5.1.7",
+ "fake-indexeddb": "^3.1.2",
+ "history": "4",
+ "jest-fetch-mock": "^3.0.3",
+ "user-event": "^4.0.0"
+ }
+}
diff --git a/website/public/_redirects b/website/public/_redirects
new file mode 100644
index 0000000..78f7f20
--- /dev/null
+++ b/website/public/_redirects
@@ -0,0 +1 @@
+/* /index.html 200
\ No newline at end of file
diff --git a/website/public/favicon.ico b/website/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..676170195fbb28140c85edbc7f84304b599d63b2
GIT binary patch
literal 103869
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
PMRc_h*Q*4h`-uC0HNeA_
literal 0
HcmV?d00001
diff --git a/website/public/icons/favicon.ico b/website/public/icons/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..676170195fbb28140c85edbc7f84304b599d63b2
GIT binary patch
literal 103869
zcmeHQ2RxPE8^1;fiBv{O(N23wOObYIrzwP{#xIeING00RQu?R$Ye_@eT4>PHkV>UA
zDKv6OOyN}J!eH0Ys5M^
zmY?;Y-c?lfRY}rS`u4FLil=ZYlRhiw{*rCGDDWsl)*Ptvx#2iAD7J*IoFjx*FA
zn=Bcr*~e_I9abr-F(-pXU)6y
zQzP5#l9!jWsczl+Q(gaRTX#M?*8bV}93R^=xzEOq_8ln%@M)9E4>M}{5MV2G+t|J;!UNIZ9?XE>HbT5cLUZ%
z<5O){%SB9U-Z=5{^g0<+@7=6!!&+Q<{@khtGae09dEItz+qllUT`er-vVGjP_Dedc
zZ4~Yo{ycNP@+rSLYdsBI`ex~rSzDmoqqFKeh)_nLUCpHkA1CI|Yz9+1SMTT$_c^X+(gigu)0-ZnLiT$q4p00(ww0xA{|}Ywo}zYZK3=~-
zZun!`>rPxmvUffCu|r#WX+-?0sXlwC^{xr5HmtzvD}AaN$0=*B_g9IUZOK|~EI+HZ
zmRHIOR_%SJxx-XuXSDjDH@;(=d!zM?yt?e|dOYxZyi%pQYrMLoj)|k*D)~%a!OGk`
zDEQzBmky(sY0JBq)!q!)^G&mT
zw9+(#NQD6-J9J;T;R5m9vnKjN%;?H~I#Dfl41dx;GA3f0)}6!A$xersUR8Thd3Dn?
zd(&(1n-O)a`{Cn?>)hmSJ$!5&7822Kd-USi(UmLfM2(4SHf+?gB-iHE45xnj7TvE;
zBrA2wCu7yu-<(!O&peV?t%X+u{oKhB2L7>jtDZewY(U~nUA1Wcj2NQ~zmcr%0UOsj
z%hk3!doO*8yh%><@vR+o-%UCcV5w%SJg}d~qZeE5xnD`q_s}}e4*2fXWM54=uU?1l
z>>l37ucH^QY}II@k2O{v;G)!yLQGRcdy0KHy?i1j*PR7O4MK8X+*86OY7vV&N}?_(bb+i
z>;^gCR|v3oboBl79br1nzaiL^muPkV{osjznj+azqV#+
zvYc(RUlv=l)%VUCs6lB*9ADvn?(*|?nd73UVL?-tESc|hRgKlq%I*Efn63e<<^{ZH
zHEs0ue$Q{&DnEbOCB!|;pY^@%+Gkp;KVJT}&}Wj>%_H{B587O5`aI6`r<+3C;~VyV
zl=m7H*)((NndbqHiklo;JDdv{cqm2TVfgCiHb-7HbJU;jRO_dfT$Dq@J@d}R=mlFe
z_|<9qjzHzFni|KJpa108$ne;Pxal1tr|vU69$7c{{t8xxa!RSY
zcQ1{R+dArN<%YHD^zmEnza{2r?e-Tf&}=oPwo$+)OqdB)G?4%?|xcg5i@*X|!$b3WLvsov#I
z>Mw)Gd9E6(s2OjzBV|O8o}qqvJS*N#(`SFe%pcV{8+q-oZQIEueC7Pa-Q$N>v(ou_
zXVZn@cS2Ot8V>H%OL1+t<%u^OBaUx-mT)v=QSBF+%2TqFy6y@zdNRyP+3fzRng67;
zTX4r`N6y{X&DUAa`WZOKamlB8Yn!jGzA!an#jG1-Ygs4dP4%RU9c!JL
z^7=#I*EWl%B&Atfvb5Yc=?tCzAa3!G8t)rE%82ppB2P!sk~3XhpoZ#Gx3SUurbdh6
zbX=-8iEy1UE$O{V?9#QbQ!;C|?x{<%`5WVP^STxqnX^|OwwMssb)>O;?Angmb5^D`
zOj|xROUGT2D^(6I&8?UHxPK;Zc>)D)~rTE_r3F!9vJMr|#F(l{M}m5(4q$y)ZvJ&bRJT1R<6ZZdyxPo~8u;+t
znzgTo%#znN|4}{ZSnS}8@%^R+b~azM)LwO+x3<@ZI@UM0u5h2<_;7ozIdzVWPutvQ
zDa&^8)pNB1A8yy0bD)y7*QT&9(^~a>`Sqq&*5aKfthBt&gv|0z^IzA#Qe&@~E8X4v
z+x^N7%HG=h+8~Xksn0@B1UKh&JSdU(>@avm9-YG}>r!DaL*C
z8u_6*&3bDo=^ox_5TN#=hi}?~Gy2U#En7EKX;rW8xVbCdFL~P~>Y3cX$2UHlyXxT$
zjb)XN4otk;CMU1ERii!gx}IvbH^3mp^RdC4_yfYDedH&q+%MPijaP>Gwf&YaCyi!Z9P;?Uw`SID_gZetY#jRd
z-i?UBFiZVgonrf3+je&XnVEy)%v*{nGYrV`+0QBcz(=Au9Od
z&F&u_vz+hk9&tCe=ZyI0Uqh>G+xRlXe%wtLl3hdjP0MSoQoOMD=&FFp
zk@cRuI~SH#?Q)e*DwVqCx>Rza_E5MM|
zs(I+kr$&x>RVC8Ru_a>UPF^<&jYv!2fP_p7$(CCm-}TQ~Y1;dL_SP)V
zk&j{20lFIYo>AEFJ0YHcVDd|J>stq
zY&^PaQgq(AoZf-0y&fvW@6j9dxSQX>$QzNja{|J)}}JLZM)&T(_AM*2Io?UK^y>8<8+ai0F?SdOdi+FY7AYD}&DslBIs
zj<&g-GTgc5K@qab`}fHO}`U=ISI*R?(SV&*}asn_12KkG?wYdFAHs+ojgXR9RGKlG~b{9$RW?
zQZ5danz^Yo-qUj5-R*6jyv)*@w=mYqRr%2CDe>3O?S0zNs*m9ndDD$eZn%7kNNx4G
znQOI`R)(oJ&R%RC@VT0+a+X^+tL4kT>~i$i?KV|862R41vw4QcA
zij&o;ZF9WGd3HHt9Cr2CKgR;}ZUv@x9AGl`TF`N?kLn?FtAB3YOa1s$I)?h@~@u>C%rYYpt)DMsuL+P5X{p8DS9
z5tFNHM>Y-mc5TqQZNBv`C45opZxj8YkG=e4b*K6hH;oR_%k065nc`5()6V#8;+2?$
zIV&vMS>LK0+^5sNJKOsy-MM(Q+mztXuV-%9KWpW?W<8#s+r_%5YqZ(&Pb1{NZ*8cK
zxrM%yR_2z_9)1htBJ=vDIcPHN>Mp-kLYF*Wc%YYjrQQlD=X64A>rT0M
zzI$-;b)TJH*@5BD``^`A)y^<#Y;2-jvQ3Rl|H;9ICv%Jkt7N#&>6C1}SbunIBiF1M
zExzB&K594SG=6}xzqGa&Sl*^9h|j)XK
zeT(#{lXGJu+H&{6(IJ1W?O7@1&8A^BRu0u80|l(Uy#`qx>1pfTm|T0ogN!0znBfn}
zv94&a*zT_f^TTL>(Ey_XMgxon7!5EQU^KvJfYAV>0Y(Fi26!|;QIsBWbBT*2?m2O3
z#HBM%YC3@alZiV`+MgpRNOU1(%09g
zs#U8-)u>T}s#mWbWol|lO`JH9+PHBem7JVhKC~}=DWryfVqzlY=;%mkYHCvC8zhpG
zlar&&&CRK>urTWP@88nbqU>@|>iFm7CU)#O%fX;QgQ)M{zl(%|
zlamwGrAwE5cl`Kqk>bmE;8MdsAt8ZMP*4zvzlMeeWo2bW&7VJ?3JwmYfWz6dXQ^Yy
zj!{8DLDbZ#Q>kXnnhA_gmxG=?dx}&BI(6z)AWh_5fvD8*x3#q`pl2E{LqkLA-+%w5
z&<_xX9zA+QO`krUs#K{G-}I2~kRd~a{uVI!6OD;T1SgTk|8jD2D7@b!n*y$3!-mv{
z4N~n_wU~?GOeRWk5XHJ9w=u^mo61&Hsvj?Qo$d6I_~#>!GZ;1)g}55#>U1x`JjErkU$NT3jP?I
zUyLT^Dvt~^m{^__F)3Ud>r@njI%@dZQDdn2mMg=M`egg1%G^dQO9y)A&I`(%9Sg5
z^5M?6uzRdE!S`NUTf124*x1;}5d2iB;17lHtCuA)*RpBTCZTY{8WKU}0P9i&J;OKy
z`WclgR~8s1CMHH!%0Q{%k9Ie=PBBj-ZYm6aEiEnfIdcJVs0#97vzkB!YBI9H1
zn7i!YJ0t^CuJHHp@ZiBg*!Jbqr%!p}0@KLI$Rc6nH%`ppU!>Z^9sqkmxZgwc@r5Cb
z`Ekd^y)f-~%w==OW$+h9>*5RZ>C-26Tb&aJtU1MgBVh<_Q+{KDad9x0RyZ;E7p|Yu
zc#1XToY>=9n6^77Z~W#cFn3iHG58myA;~{SI}K|JIo4X!i@{%Z*6PuiiSw7ipTVCD
zm5N5B0LtWM7rZ#wi-PrDwEOt+qd4CuSxc+Jz5lWXkc9bV@RzWH%Xsn({$(5>3A4xG
zFJT3j@#Go&%Q!$1W{<&N!U`_q$uszuaeySu9)rJx6H5>ToPCCT6~LtsgO
zRR(_vC{>1%{QC7P+YTIT!O>2bJ?K&pk@@~hLFr{EO9uZk1cnq?Vel7&(qZ44bKfDZ
zU%h&jpYq|uhk2f#Jb5zzxfCxL{KcUB_U+pj=!v;}B?`uE|(hh;%x=KEh3P>>!E
z4F1y7eObtv!Cy!eU|$S#_&8?{_C75vEZFB!>*?uH>gwtgeZ~yz1;eg6Yy#sv5S)7e
zo3}Xc3g;l<45}wjo=~~DxkBbF(O?Yzg{T*2!D4Kub?erAZNqW*=|#DRt+5s@T2Qb%
zjIn@o=gv`SX=#OUBauf8{#9YEMA$N5yE;3mNRRD1mxKi;8oqqoSfHS65edKY$x=e)q6vb@S#;(K7lkbcMwq
zxYA=b+E*4#o>{-aIFd+}C~k_AO5S6&C+_^XBo@C(aKmi-dM2#tHeIQ9+l5Vyr2vu=oo)
z?+xdNmSytu^W$4?Q3iVS=picW6&8QY$N*;
z$B!S+7yqqWw{obbY+mv^=Z-GJyLRmoh3N{5|3Cly!xw*VZ*NibC$+Hc+O^}$KjadD
zDlGoN!NGj>H+uAF5!5HWpqQ_q-+9ho>^&5LDlGmO`{UFbeO;?otwP6iMbNE8gJRr|
z=9%-?+S*!_d@C&eP*Rg7P5A0im@^+mQL-e$zI*qMFYg%B6oD!<{u?%I;H$$?qeh9K
zLg@rWJD>Btr>}8_od{H+@z2iAX0PGH91o|%_^-I5r~xMWlmCt
zVSXC8QcV0k_$9{p$(#n36chhCMADx!CoRJ;rv~!Kjd5}CM;sD&9cN<9wajT?Q*p8X
zi@zF?xI!PEEpv$f$M6GkS*0ZS|1AT0Od0r3sWNf}!$Y-8iai~lq=kB5BXMQIpatQB
zA^sY`_kIg;C4TQ|>^Xl)y7h?jBJLV-I8-@@IFt#-Nk<3JKX8a9ZYgmMIPqex84WNR
zU^KvJfYAV>0Y(Fi1{e)68elZQsR7bwC|!ENnR`{zo8-J#ps?WaKa!(<|968cp8LFl
z&pny@0t#XDQ9vCmMjwnmI5ohO2TpCUnD?Wk-;bicSKQw>p6}laPC&@_QNq-iWM@vC
z7jbf|y5v70a~A80#vjMNXZ|r7U^Kv^0rF}p5!au%xx~$vapy?*7kd7HgzZFJGI8aJ
zei30d6Q{%jHirj!UZ1!O;>s&+;SiVSk_au50TLNh5)8gJ;t~9>2YL2Z24xbJ<*9|p
z|3lQoWH7u3BR_u)5G)JD|NHmv6l|}+P66yMXlZFt*l!J6wy=SV^Hn~6#I$vprefib
zbHHFHoM^64SEWi73bqLfvn?%U?T9QtY+=JTIxRbFuVT+C?6AQWpSig?rKYBq{~Y0P
z4#uxv`0N%WoQ+YZP8|w+V5LMN%MZI^wCn>044`1YgPXp6`^G*a2=4P
zL4&xHD6M;u<%eA&$OPFq&&K1&Fzm0;_R(R}E;cr{Krqw~u+Q-H>C*!KrFbK<{N1{B
zW6O`TB88#Nn>Xi|0TFFF36p$D!-yLh>{;sW
z-Mc*JXT5y+l5LkC?*?o;Nti^IfAHYJY*}!=kFXRF5RhL6aAp8qC*T+LAAQC&zL}h7
zm*Abj8RW1hE+GMVPRqU
ze4-2sL$K@4DRbk-jVX6`cM3L&X@7lvefBjlx3#rpx1S6Q4Dz4DhPVVsWcl%Jt6H@x
zn=fI%+gGk!$(N&T+qM+a0O9PsHf`GEKZ7nf?cU(~LF;GEoH-H@pAuPqFtKFG5(;=3
z8yiz!zI-W?uLB1T$CEUDCbO{LTb=|8_b3(lO-xMKGV=TWXJuvO%Rh1AL>_MG?=9?vN`NFT
zKh85iod*2T7ve`aqXW;KoSb+b%ewsN9~?PygzetHf6pTyJaA4S!rZxYhv&Jh%g-Z_
zh#nPP{_yZ{YRi@_?D8R^>Vyd{@%Nt}6F5H?XRAr*o6avwL4Hz^f7!BS?D|gF_LCsy
z#o#X$`Eh0s`aG{+zZQcCB}zgn@|TEX@kVCkCk+pNMAR&?PPcd=EKzC<{`|lveq{~+
zjvYHvm`~u>M<_uFkQw}&H*e1VW{TOSv&_hk^Ni-tpU*yLvIKZ@N0k}*xfvJJeVLcv
z($bPWzqWKKw*FG=xirkT@#`xWLnmckew^cg>UheODQxk&ckj-=$9d{vpfWE%?7$Bj
zHjJXrGg`E05&IrvkYb=RFTWUMEG&sKCI5p552)qKms2?VgI-gC`E`t+;e2D94~qGt
zAw!0+zXdoWir*YsVFGhKD--hLtXx57q2b*x%3&@Vb166jJT*0yOTW(7LKbD&O(b3WQ*rR-l>wIpkF~PhXT^2B>2K@*ismXuv;K6(zv5rWbgf$fO94+*K
zvqlAw)Z{;L;zYjuy?gf-z+Uma(N3bvKp|~KsmYHuPc(m+=NC7jEkUQ>v}sd8$xBUs
zFoZgf=26`18!|I9^V3E?0!XUzqpm`~4z3tyJ_``(kN5ri_w(g1WbJ@d;q>LheiCd>R>nX6LJ3%*OZo#
z`DHY~XyA_q$a}$;vj05p{^iea)fWYSmH*x|zkl>nR`pQzFMfu6K_;HC6VZw|TjKKV
zMAT>OL=bh9X(G_oCT=KkHf79V(YlHt<&kG-XL=ArslWr~fG%FV$ioKq+hQynGGPBY#z<+}vuBT__Zk+-KlT%2F3-rw
zh-QoDHzOm1!u~nPfOn%5sYw3Q)6?0yFJ!!(%`Ux%(89ukI&doFYL?%fpPgD_u(
zJ)_ubi8;)?Jak=4OhxjKeWsX;!`u<~J}u7Nv1bW$?)2IO`u)aS?6YUjsCxD4<&Qn%
z*|>4z#34hG{DVh&9RcP)3L~tg0B?BDvA(83g9hw-x@=)zF4o>a9|(u})e@3W{I6fX
zo}C}(K1l9-feq$0@vhMGyVz%?uCC6G3;Zw#2->-GC)*GEmP<%N@sBzsFfg!CeZ77A
zmclv(>}lgHXP5_tt~vKbpgh43>px3KLh&z59`t@rd>c4v?%cU-UVVLi^SwWP`o#8Y
z)TmLuUokJl#{cx`)7fPuGBUCND_DDkGQ_!04|NgrUzYes8v}I}w@ndz|KN{bn#v;o
zG;hNEVm~m-721F$B(aqrPCn31)z;RgP;W_?B*H(+2i6gZd;XP=E+oP~pL`_c#qcjF
z%?g*URP~RxHrAJ6T|qHPs{BuyG>QEkyL9PNG1;f2%0JpnSod~VIg?%uvH8^Y5EO!3_;~R8;Sx8wV&i{=YH`ulhQAUJu7^lHHe^*ym>fu8i
z=UFI8l>gJGPv_fe1Mld=j~_pt@+9li{r&yf`w}o7F>>Tcw!J30uSv^^u>jcl!}yE<
zk{JIOAE9j$V!seC7E>-^X))DQh2
z^tsWeMmjDoF8snvjDLJnkuUbBU~H(kG-JjLb~@-!@UeL&G5(X2liB$~hNPsV;qA#ONr5n()uizLcF#(pto#m}B57asy|@!i1w
zs7{?a@oOhZlz)LtiSGeF{*Q|3UMn|J!2v&$aPOgTs$+
z{@Cw^J__bUxe56~2bh0{j%d4&W@cvWex-=>Uwre23|O0iwN%^$8`CJ42!lCw@PaX8
z5bW+iKOzv{{0oZ@xnc7hgz*RDk9lSg{9!v+1QIEK^!s2}3H@N2@D9LEJ7~v_9TLr-
z&Qs7Y>}+F>P6QH3e$I5U20;vSfyK@r{ow3uY_%y76+8bD<@#S_vFDGnNZY{qFB^eZ
zV$UCIF)^l*)362%yrKQTMa7n%wo4$)dJcT2u$~m-&RD0#LB-A=Z8Yc{=cWilwEcLr
zRXC{F`6CT{YrMR?IFb{5Nq?)k+7uG;mh&g}KvmoHyt&sU>vK!3Lg%8!r!25s{S>%lv8=)j)K!@3*T3>QH8@iBLZbs`}2
zZ_$p0-9+$Hh~3ou`0R*(|6#oV=HM`=g6|H$^UMAQq&-CV#bkdK@`|I5#d*2i
PMRc_h*Q*4h`-uC0HNeA_
literal 0
HcmV?d00001
diff --git a/website/public/icons/icon.png b/website/public/icons/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..8af9e9d108493baa60823433095e92d7f8356215
GIT binary patch
literal 14258
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
literal 0
HcmV?d00001
diff --git a/website/public/icons/icon144.png b/website/public/icons/icon144.png
new file mode 100644
index 0000000000000000000000000000000000000000..400249568ab998565b751204c939a869f2992794
GIT binary patch
literal 3737
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
NtMyQ`T;pl@{{Tf225JBR
literal 0
HcmV?d00001
diff --git a/website/public/icons/icon168.png b/website/public/icons/icon168.png
new file mode 100644
index 0000000000000000000000000000000000000000..b2d19cef887e1b0b096ffd8f4108748cb34d7e90
GIT binary patch
literal 4313
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