diff --git a/.metadata b/.metadata deleted file mode 100644 index db36dec..0000000 --- a/.metadata +++ /dev/null @@ -1,36 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - channel: stable - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: android - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: ios - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: web - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/Dockerfile b/Dockerfile index 736e6fc..02ad197 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,22 @@ -FROM passsy/flutterw:base-latest +FROM archlinux +WORKDIR /app +RUN pacman --noconfirm -Syu +ARG FIREBASE_KEY +ARG FIREBASE_MESSAGING +ARG FIREBASE_STORAGE +ARG FIREBASE_APPID +ARG FIREBASE_AUTH +ARG FIREBASE_ID +ENV FIREBASE_KEY=$FIREBASE_KEY +ENV FIREBASE_MESSAGING=$FIREBASE_MESSAGING +ENV FIREBASE_STORAGE=$FIREBASE_STORAGE +ENV FIREBASE_APPID=$FIREBASE_APPID +ENV FIREBASE_AUTH=$FIREBASE_AUTH +ENV FIREBASE_ID=$FIREBASE_ID COPY . . +RUN pacman --noconfirm -S nodejs npm git base-devel unzip +RUN npm i -g pnpm RUN ./flutterw config --no-analytics -ENTRYPOINT ./flutterw run --release --web-port=80 --web-hostname 0.0.0.0 -d web-server +RUN cd api && pnpm i && pnpm run build +ENTRYPOINT PORT=80 node /app/api/build/index.js EXPOSE 80 \ No newline at end of file diff --git a/README.md b/README.md index 7571ac9..a607d33 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Vydáno pod licencí AGPL verze 3 1. Clone 2. Build -## Obrázky +## Obrázky (verze 1.x) ![Screenshot přihlašovací obrazovky](images/01.png) ![Screenshot přihlašovací obrazovky](images/02.png) ![Screenshot přihlašovací obrazovky](images/03.png) diff --git a/api/.eslintrc.json b/api/.eslintrc.json new file mode 100644 index 0000000..58ce467 --- /dev/null +++ b/api/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "env": { + "es2021": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "overrides": [ + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + }, + "ignorePatterns":[ + "src/public" + ] +} diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..181500d --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,181 @@ +# Created by https://www.toptal.com/developers/gitignore/api/linux,visualstudiocode,node +# Edit at https://www.toptal.com/developers/gitignore?templates=linux,visualstudiocode,node + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# 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 +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/linux,visualstudiocode,node +build +src/public/* +!src/public/.gitkeep \ No newline at end of file diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..8526b27 --- /dev/null +++ b/api/README.md @@ -0,0 +1,26 @@ +# Tourdeappka NodeJS API server +(c) 2023 Matyáš Caras a Richard Pavlikán + +## Požadavky +- [NodeJS](https://nodejs.org) LTS verze (16+) +- [pnpm](https://pnpm.io) + +## Jak spustit +1. Nainstaluj NodeJS +2. Nainstaluj pNPM +3. Stáhni repozitář +4. Nainstaluj závislosti (`pnpm i`) +### K vývoji +5. Vytvoř soubor `.env`: +```js +FIREBASE_KEY=klic +FIREBASE_AUTH=nejakaurl +FIREBASE_ID=idcko +FIREBASE_STORAGE=nejakaurl +FIREBASE_MESSAGING=idcko +FIREBASE_APPID=idcko +``` +6. Spusť pomocí `pnpm run dev` +### Live server +5. Ulož proměnné dle předchozí struktury jako systémové proměnné +6. Spusť pomocí `pnpm start` \ No newline at end of file diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..2e6650a --- /dev/null +++ b/api/package.json @@ -0,0 +1,30 @@ +{ + "name": "api", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "NODE_DEBUG=true ts-node ./src", + "build": "pnpm run clean && pnpm run flutter && tsc -p tsconfig.json && cp -r ./src/public ./build/public", + "clean": "rm -rf ./build", + "start": "pnpm run build && node ./build", + "flutter": "cd .. && ./flutterw clean && ./flutterw pub get && ./flutterw build web --release && rm -rf ./api/src/public/* && mv ./build/web/* ./api/src/public" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/node": "^18.14.0", + "@typescript-eslint/eslint-plugin": "^5.52.0", + "@typescript-eslint/parser": "^5.52.0", + "dotenv": "^16.0.3", + "eslint": "^8.34.0", + "ts-node": "^10.9.1", + "typescript": "^4.9.5" + }, + "dependencies": { + "@fastify/static": "^6.9.0", + "fastify": "^4.13.0", + "firebase": "^9.17.1" + } +} diff --git a/api/pnpm-lock.yaml b/api/pnpm-lock.yaml new file mode 100644 index 0000000..4930812 --- /dev/null +++ b/api/pnpm-lock.yaml @@ -0,0 +1,2209 @@ +lockfileVersion: 5.4 + +specifiers: + '@fastify/static': ^6.9.0 + '@types/node': ^18.14.0 + '@typescript-eslint/eslint-plugin': ^5.52.0 + '@typescript-eslint/parser': ^5.52.0 + dotenv: ^16.0.3 + eslint: ^8.34.0 + fastify: ^4.13.0 + firebase: ^9.17.1 + ts-node: ^10.9.1 + typescript: ^4.9.5 + +dependencies: + '@fastify/static': 6.9.0 + fastify: 4.13.0 + firebase: 9.17.1 + +devDependencies: + '@types/node': 18.14.0 + '@typescript-eslint/eslint-plugin': 5.52.0_6cfvjsbua5ptj65675bqcn6oza + '@typescript-eslint/parser': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm + dotenv: 16.0.3 + eslint: 8.34.0 + ts-node: 10.9.1_tncu2ai53lzgmizdedur7lbibe + typescript: 4.9.5 + +packages: + + /@cspotcode/source-map-support/0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@eslint/eslintrc/1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.1 + globals: 13.20.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@fastify/accept-negotiator/1.1.0: + resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==} + engines: {node: '>=14'} + dev: false + + /@fastify/ajv-compiler/3.5.0: + resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1 + fast-uri: 2.2.0 + dev: false + + /@fastify/deepmerge/1.3.0: + resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + dev: false + + /@fastify/error/3.2.0: + resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==} + dev: false + + /@fastify/fast-json-stringify-compiler/4.2.0: + resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==} + dependencies: + fast-json-stringify: 5.5.0 + dev: false + + /@fastify/send/2.0.1: + resolution: {integrity: sha512-8jdouu0o5d0FMq1+zCKeKXc1tmOQ5tTGYdQP3MpyF9+WWrZT1KCBdh6hvoEYxOm3oJG/akdE9BpehLiJgYRvGw==} + dependencies: + '@lukeed/ms': 2.0.1 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.0 + mime: 3.0.0 + dev: false + + /@fastify/static/6.9.0: + resolution: {integrity: sha512-9SBVNJi2+KTnfiW1WjiVXDsmUxliNI54OF1eOiaop264dh8FwXSuLmO62JXvx7+VD0vQXEqsyRbFCYUJ9aJxng==} + dependencies: + '@fastify/accept-negotiator': 1.1.0 + '@fastify/send': 2.0.1 + content-disposition: 0.5.4 + fastify-plugin: 4.5.0 + glob: 8.1.0 + p-limit: 3.1.0 + readable-stream: 4.3.0 + dev: false + + /@firebase/analytics-compat/0.2.3_5qoz5voa527ucsvtk67mpz25ru: + resolution: {integrity: sha512-HmvbB4GMgh8AUlIDIo/OuFENLCGRXxMvtOueK+m8+DcfqBvG+mkii0Mi9ovo0TnMM62cy3oBYG7PHdjIQNLSLA==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/analytics': 0.9.3_@firebase+app@0.9.3 + '@firebase/analytics-types': 0.8.0 + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/analytics-types/0.8.0: + resolution: {integrity: sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==} + dev: false + + /@firebase/analytics/0.9.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-XdYHBi6RvHYVAHGyLxXX0uRPwZmGeqw1JuWS1rMEeRF/jvbxnrL81kcFAHZVRkEvG9bXAJgL2fX9wmDo3e622w==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/installations': 0.6.3_@firebase+app@0.9.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + dev: false + + /@firebase/app-check-compat/0.3.3_5qoz5voa527ucsvtk67mpz25ru: + resolution: {integrity: sha512-25AQ4W7WUL8OWas40GsABuNU622Dm1ojbfeZ03uKtLj5Af7FerJ25u7zkgm+11pc6rpr5v8E5oxEG9vmNRndEA==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-check': 0.6.3_@firebase+app@0.9.3 + '@firebase/app-check-types': 0.5.0 + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/app-check-interop-types/0.2.0: + resolution: {integrity: sha512-+3PQIeX6/eiVK+x/yg8r6xTNR97fN7MahFDm+jiQmDjcyvSefoGuTTNQuuMScGyx3vYUBeZn+Cp9kC0yY/9uxQ==} + dev: false + + /@firebase/app-check-types/0.5.0: + resolution: {integrity: sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==} + dev: false + + /@firebase/app-check/0.6.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-T9f9ceFLs7x4D2T6whu5a6j7B3qPuYHiZHZxW6DkMh/FoMmRA4/q/HVyu01i9+LyJJx2Xdo6eCcj6ofs9YZjqA==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + dev: false + + /@firebase/app-compat/0.2.3: + resolution: {integrity: sha512-sX6rD1KFX6K2CuCnQvc9jZLOgAFZ+sv2jKKahIl4SbTM561D682B8n4Jtx/SgDrvcTVTdb05g4NhZOws9hxYxA==} + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + dev: false + + /@firebase/app-types/0.9.0: + resolution: {integrity: sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==} + dev: false + + /@firebase/app/0.9.3: + resolution: {integrity: sha512-G79JUceVDaHRZ4WkA11GyVldVXhdyRJRwWVQFFvAAVfQJLvy2TA6lQjeUn28F6FmeUWxDGwPC30bxCRWq7Op8Q==} + dependencies: + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + idb: 7.0.1 + tslib: 2.5.0 + dev: false + + /@firebase/auth-compat/0.3.3_2nquphbz2k7fjevr4yz5c7cjmi: + resolution: {integrity: sha512-9asUuGtkzUVELH3LYXdiom1nVVV9bqEPqzHohanoofHL/oVTNcHZ4AQ5CXjNATfb6c1WH32U+nEuPiYg26UUIw==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.2.3 + '@firebase/auth': 0.21.3_@firebase+app@0.9.3 + '@firebase/auth-types': 0.12.0_rls6ojzmqtv4do6kouxcb5gfn4 + '@firebase/component': 0.6.3 + '@firebase/util': 1.9.2 + node-fetch: 2.6.7 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + - encoding + dev: false + + /@firebase/auth-interop-types/0.2.1: + resolution: {integrity: sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==} + dev: false + + /@firebase/auth-types/0.12.0_rls6ojzmqtv4do6kouxcb5gfn4: + resolution: {integrity: sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + dependencies: + '@firebase/app-types': 0.9.0 + '@firebase/util': 1.9.2 + dev: false + + /@firebase/auth/0.21.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-HPbcwgArLBVTowFcn4qaQr6LCx7BidI9yrQ5MRbQNv4PsgK/3UGpzCYaNPPbvgr9fe+0jNdJO+uC0+dk4xIzCQ==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + node-fetch: 2.6.7 + tslib: 2.5.0 + transitivePeerDependencies: + - encoding + dev: false + + /@firebase/component/0.6.3: + resolution: {integrity: sha512-rnhq5SOsB5nuJphZF50iwqnBiuuyg9kdnlUn1rBrKfu7/cUVJZF5IG1cWrL0rXXyiZW1WBI/J2pmTvVO8dStGQ==} + dependencies: + '@firebase/util': 1.9.2 + tslib: 2.5.0 + dev: false + + /@firebase/database-compat/0.3.3: + resolution: {integrity: sha512-r+L9jTbvsnb7sD+xz6UKU39DgBWqB2pyjzPNdBeriGC9Ssa2MAZe0bIqjCQg51RRXYc/aa/zK1Q2/4uesZeVgQ==} + dependencies: + '@firebase/component': 0.6.3 + '@firebase/database': 0.14.3 + '@firebase/database-types': 0.10.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + dev: false + + /@firebase/database-types/0.10.3: + resolution: {integrity: sha512-Hu34CDhHYZsd2eielr0jeaWrTJk8Hz0nd7WsnYDnXtQX4i49ppgPesUzPdXVBdIBLJmT0ZZRvT7qWHknkOT+zg==} + dependencies: + '@firebase/app-types': 0.9.0 + '@firebase/util': 1.9.2 + dev: false + + /@firebase/database/0.14.3: + resolution: {integrity: sha512-J76W6N7JiVkLaAtPyjaGRkrsIu9pi6iZikuGGtGjqvV19vkn7oiL4Hbo5uTYCMd4waTUWoL9iI08eX184W+5GQ==} + dependencies: + '@firebase/auth-interop-types': 0.2.1 + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + faye-websocket: 0.11.4 + tslib: 2.5.0 + dev: false + + /@firebase/firestore-compat/0.3.3_2nquphbz2k7fjevr4yz5c7cjmi: + resolution: {integrity: sha512-fMTsSC0s2cF5w2+JoB0dWD/o4kXtLrUCPGnZPuz4S0bqTN2t0vHr3gdAsQLtnadgwB78ACtinYmf4Udwx7TzDg==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/firestore': 3.8.3_@firebase+app@0.9.3 + '@firebase/firestore-types': 2.5.1_rls6ojzmqtv4do6kouxcb5gfn4 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + - encoding + dev: false + + /@firebase/firestore-types/2.5.1_rls6ojzmqtv4do6kouxcb5gfn4: + resolution: {integrity: sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + dependencies: + '@firebase/app-types': 0.9.0 + '@firebase/util': 1.9.2 + dev: false + + /@firebase/firestore/3.8.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-4xR3Mqj95bxHg3hZnz0O+LQrHkjq+siT2y+B9da6u68qJ8bzzT42JaFgd1vifhbBpVbBzpFaS2RuCq2E+kGv9g==} + engines: {node: '>=10.10.0'} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + '@firebase/webchannel-wrapper': 0.9.0 + '@grpc/grpc-js': 1.7.3 + '@grpc/proto-loader': 0.6.13 + node-fetch: 2.6.7 + tslib: 2.5.0 + transitivePeerDependencies: + - encoding + dev: false + + /@firebase/functions-compat/0.3.3_5qoz5voa527ucsvtk67mpz25ru: + resolution: {integrity: sha512-UIAJ2gzNq0p/61cXqkpi9DnlQt0hdlGqgmL5an7KuJth2Iv5uGpKg/+OapAZxPuiUNZgTEyZDB7kNBHvnxWq5w==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/functions': 0.9.3_@firebase+app@0.9.3 + '@firebase/functions-types': 0.6.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + - encoding + dev: false + + /@firebase/functions-types/0.6.0: + resolution: {integrity: sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==} + dev: false + + /@firebase/functions/0.9.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-tPJgYY2ROQSYuzvgxZRoHeDj+Ic07/bWHwaftgTriawtupmFOkt5iikuhJSJUhaOpFh9TB335OvCXJw1N+BIlQ==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/app-check-interop-types': 0.2.0 + '@firebase/auth-interop-types': 0.2.1 + '@firebase/component': 0.6.3 + '@firebase/messaging-interop-types': 0.2.0 + '@firebase/util': 1.9.2 + node-fetch: 2.6.7 + tslib: 2.5.0 + transitivePeerDependencies: + - encoding + dev: false + + /@firebase/installations-compat/0.2.3_2nquphbz2k7fjevr4yz5c7cjmi: + resolution: {integrity: sha512-K9rKM/ym06lkpaKz7bMLxzHK/HEk65XfLJBV+dJkIuWeO0EqqC9VFGrpWAo0QmgC4BqbU58T6VBbzoJjb0gaFw==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/installations': 0.6.3_@firebase+app@0.9.3 + '@firebase/installations-types': 0.5.0_@firebase+app-types@0.9.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + dev: false + + /@firebase/installations-types/0.5.0_@firebase+app-types@0.9.0: + resolution: {integrity: sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==} + peerDependencies: + '@firebase/app-types': 0.x + dependencies: + '@firebase/app-types': 0.9.0 + dev: false + + /@firebase/installations/0.6.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-20JFWm+tweNoRjRbz8/Y4I7O5pUJGZsFKCkLl1qNxfNYECSfrZUuozIDJDZC/MeVn5+kB9CwjThDlgQEPrfLdg==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/util': 1.9.2 + idb: 7.0.1 + tslib: 2.5.0 + dev: false + + /@firebase/logger/0.4.0: + resolution: {integrity: sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==} + dependencies: + tslib: 2.5.0 + dev: false + + /@firebase/messaging-compat/0.2.3_5qoz5voa527ucsvtk67mpz25ru: + resolution: {integrity: sha512-MmuuohXV2YRzIoJmDngI5qqO/cF2q7SdAaw7k4r61W3ReJy7x4/rtqrIvwNVhM6X/X8NFGBbsYKsCfRHWjFdkg==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/messaging': 0.12.3_@firebase+app@0.9.3 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/messaging-interop-types/0.2.0: + resolution: {integrity: sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==} + dev: false + + /@firebase/messaging/0.12.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-a3ZKcGDiV2sKmQDB56PpgL1yjFxXCtff2+v1grnAZZ4GnfNQ74t2EHCbmgY7xRX7ThzMqug54oxhuk4ur0MIoA==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/installations': 0.6.3_@firebase+app@0.9.3 + '@firebase/messaging-interop-types': 0.2.0 + '@firebase/util': 1.9.2 + idb: 7.0.1 + tslib: 2.5.0 + dev: false + + /@firebase/performance-compat/0.2.3_5qoz5voa527ucsvtk67mpz25ru: + resolution: {integrity: sha512-I3rqZsIhauXn4iApfj1ttKQdlti/r8OZBG4YK10vxKSdhAzTIDWDKEsdoCXvvKLwplcMv36sM3WPAPGQLqY5MQ==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/performance': 0.6.3_@firebase+app@0.9.3 + '@firebase/performance-types': 0.2.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/performance-types/0.2.0: + resolution: {integrity: sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==} + dev: false + + /@firebase/performance/0.6.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-NQmQN6Ete7i9jz1mzULJZEGvsOmwwdUy6vpqnhUxSFMYPnlBKjX+yypCUUJDDN5zff5+kfwSD1qCyUAaS0xWUA==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/installations': 0.6.3_@firebase+app@0.9.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + dev: false + + /@firebase/remote-config-compat/0.2.3_5qoz5voa527ucsvtk67mpz25ru: + resolution: {integrity: sha512-w/ZL03YgYaXq03xIRyJ5oPhXZi6iDsY/v0J9Y7I7SqxCYytEnHVrL9nvBqd9R94y5LRAVNPCLokJeeizaUz4VQ==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/logger': 0.4.0 + '@firebase/remote-config': 0.4.3_@firebase+app@0.9.3 + '@firebase/remote-config-types': 0.3.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/remote-config-types/0.3.0: + resolution: {integrity: sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==} + dev: false + + /@firebase/remote-config/0.4.3_@firebase+app@0.9.3: + resolution: {integrity: sha512-Q6d4jBWZoNt6SYq87bjtDGUHFkKwAmGnNjWyRjl14AZqE1ilgd9NZHmutharlYJ3LvxMsid80HdK5SgGEpIPfg==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/installations': 0.6.3_@firebase+app@0.9.3 + '@firebase/logger': 0.4.0 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + dev: false + + /@firebase/storage-compat/0.3.1_2nquphbz2k7fjevr4yz5c7cjmi: + resolution: {integrity: sha512-6HaTvWsT5Yy3j4UpCZpMcFUYEkJ2XYWukdyTl02u6VjSBRLvkhOXPzEfMvgVWqhnF/rYVfPdjrZ904wk5OxtmQ==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.2.3 + '@firebase/component': 0.6.3 + '@firebase/storage': 0.11.1_@firebase+app@0.9.3 + '@firebase/storage-types': 0.8.0_rls6ojzmqtv4do6kouxcb5gfn4 + '@firebase/util': 1.9.2 + tslib: 2.5.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + - encoding + dev: false + + /@firebase/storage-types/0.8.0_rls6ojzmqtv4do6kouxcb5gfn4: + resolution: {integrity: sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + dependencies: + '@firebase/app-types': 0.9.0 + '@firebase/util': 1.9.2 + dev: false + + /@firebase/storage/0.11.1_@firebase+app@0.9.3: + resolution: {integrity: sha512-Xv8EG2j52ugF2xayBz26U9J0VBXHXPMVxSN+ph3R3BSoHxvMLaPu+qUYKHavSt+zbcgPH2GyBhrCdJK6SaDFPA==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.9.3 + '@firebase/component': 0.6.3 + '@firebase/util': 1.9.2 + node-fetch: 2.6.7 + tslib: 2.5.0 + transitivePeerDependencies: + - encoding + dev: false + + /@firebase/util/1.9.2: + resolution: {integrity: sha512-9l0uMGPGw3GsoD5khjMmYCCcMq/OR/OOSViiWMN+s2Q0pxM+fYzrii1H+r8qC/uoMjSVXomjLZt0vZIyryCqtQ==} + dependencies: + tslib: 2.5.0 + dev: false + + /@firebase/webchannel-wrapper/0.9.0: + resolution: {integrity: sha512-BpiZLBWdLFw+qFel9p3Zs1jD6QmH7Ii4aTDu6+vx8ShdidChZUXqDhYJly4ZjSgQh54miXbBgBrk0S+jTIh/Qg==} + dev: false + + /@grpc/grpc-js/1.7.3: + resolution: {integrity: sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==} + engines: {node: ^8.13.0 || >=10.10.0} + dependencies: + '@grpc/proto-loader': 0.7.5 + '@types/node': 18.14.0 + dev: false + + /@grpc/proto-loader/0.6.13: + resolution: {integrity: sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==} + engines: {node: '>=6'} + hasBin: true + dependencies: + '@types/long': 4.0.2 + lodash.camelcase: 4.3.0 + long: 4.0.0 + protobufjs: 6.11.3 + yargs: 16.2.0 + dev: false + + /@grpc/proto-loader/0.7.5: + resolution: {integrity: sha512-mfcTuMbFowq1wh/Rn5KQl6qb95M21Prej3bewD9dUQMurYGVckGO/Pbe2Ocwto6sD05b/mxZLspvqwx60xO2Rg==} + engines: {node: '>=6'} + hasBin: true + dependencies: + '@types/long': 4.0.2 + lodash.camelcase: 4.3.0 + long: 4.0.0 + protobufjs: 7.2.2 + yargs: 16.2.0 + dev: false + + /@humanwhocodes/config-array/0.11.8: + resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: true + + /@jridgewell/trace-mapping/0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /@lukeed/ms/2.0.1: + resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==} + engines: {node: '>=8'} + dev: false + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@protobufjs/aspromise/1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64/1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen/2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter/1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch/1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float/1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire/1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path/1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool/1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8/1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + + /@tsconfig/node10/1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12/1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14/1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16/1.0.3: + resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + dev: true + + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + dev: true + + /@types/long/4.0.2: + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + dev: false + + /@types/node/18.14.0: + resolution: {integrity: sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==} + + /@types/semver/7.3.13: + resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} + dev: true + + /@typescript-eslint/eslint-plugin/5.52.0_6cfvjsbua5ptj65675bqcn6oza: + resolution: {integrity: sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm + '@typescript-eslint/scope-manager': 5.52.0 + '@typescript-eslint/type-utils': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm + '@typescript-eslint/utils': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm + debug: 4.3.4 + eslint: 8.34.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + natural-compare-lite: 1.4.0 + regexpp: 3.2.0 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser/5.52.0_7kw3g6rralp5ps6mg3uyzz6azm: + resolution: {integrity: sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.52.0 + '@typescript-eslint/types': 5.52.0 + '@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.5 + debug: 4.3.4 + eslint: 8.34.0 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager/5.52.0: + resolution: {integrity: sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.52.0 + '@typescript-eslint/visitor-keys': 5.52.0 + dev: true + + /@typescript-eslint/type-utils/5.52.0_7kw3g6rralp5ps6mg3uyzz6azm: + resolution: {integrity: sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.5 + '@typescript-eslint/utils': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm + debug: 4.3.4 + eslint: 8.34.0 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types/5.52.0: + resolution: {integrity: sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree/5.52.0_typescript@4.9.5: + resolution: {integrity: sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.52.0 + '@typescript-eslint/visitor-keys': 5.52.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils/5.52.0_7kw3g6rralp5ps6mg3uyzz6azm: + resolution: {integrity: sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.52.0 + '@typescript-eslint/types': 5.52.0 + '@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.5 + eslint: 8.34.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.34.0 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys/5.52.0: + resolution: {integrity: sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.52.0 + eslint-visitor-keys: 3.3.0 + dev: true + + /abort-controller/3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + + /abstract-logging/2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + dev: false + + /acorn-jsx/5.3.2_acorn@8.8.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.2 + dev: true + + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn/8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv-formats/2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + dev: false + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ajv/8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: false + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /archy/1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + dev: false + + /arg/4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-union/2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /atomic-sleep/1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + dev: false + + /avvio/8.2.1: + resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} + dependencies: + archy: 1.0.0 + debug: 4.3.4 + fastq: 1.15.0 + transitivePeerDependencies: + - supports-color + dev: false + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js/1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: false + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /buffer/6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /cliui/7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: false + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /content-disposition/0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /diff/4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dotenv/16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + dev: true + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: false + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: false + + /escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils/3.0.0_eslint@8.34.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.34.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys/2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint/8.34.0: + resolution: {integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.4.1 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.34.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.1 + esquery: 1.4.2 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.20.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-sdsl: 4.3.0 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree/9.4.1: + resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.2 + acorn-jsx: 5.3.2_acorn@8.8.2 + eslint-visitor-keys: 3.3.0 + dev: true + + /esquery/1.4.2: + resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + + /estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /event-target-shim/5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + + /events/3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: false + + /fast-content-type-parse/1.0.0: + resolution: {integrity: sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA==} + dev: false + + /fast-decode-uri-component/1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + dev: false + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + /fast-glob/3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-json-stringify/5.5.0: + resolution: {integrity: sha512-rmw2Z8/mLkND8zI+3KTYIkNPEoF5v6GqDP/o+g7H3vjdWjBwuKpgAYFHIzL6ORRB+iqDjjtJnLIW9Mzxn5szOA==} + dependencies: + '@fastify/deepmerge': 1.3.0 + ajv: 8.12.0 + ajv-formats: 2.1.1 + fast-deep-equal: 3.1.3 + fast-uri: 2.2.0 + rfdc: 1.3.0 + dev: false + + /fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fast-querystring/1.1.1: + resolution: {integrity: sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q==} + dependencies: + fast-decode-uri-component: 1.0.1 + dev: false + + /fast-redact/3.1.2: + resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} + engines: {node: '>=6'} + dev: false + + /fast-uri/2.2.0: + resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} + dev: false + + /fastify-plugin/4.5.0: + resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} + dev: false + + /fastify/4.13.0: + resolution: {integrity: sha512-p9ibdFWH3pZ7KPgmfHPKGUy2W4EWU2TEpwlcu58w4CwGyU3ARFfh2kwq6zpZ5W2ZGVbufi4tZbqHIHAlX/9Z/A==} + dependencies: + '@fastify/ajv-compiler': 3.5.0 + '@fastify/error': 3.2.0 + '@fastify/fast-json-stringify-compiler': 4.2.0 + abstract-logging: 2.0.1 + avvio: 8.2.1 + fast-content-type-parse: 1.0.0 + find-my-way: 7.4.0 + light-my-request: 5.9.1 + pino: 8.10.0 + process-warning: 2.1.0 + proxy-addr: 2.0.7 + rfdc: 1.3.0 + secure-json-parse: 2.7.0 + semver: 7.3.8 + tiny-lru: 10.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /fastq/1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + + /faye-websocket/0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + dependencies: + websocket-driver: 0.7.4 + dev: false + + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-my-way/7.4.0: + resolution: {integrity: sha512-JFT7eURLU5FumlZ3VBGnveId82cZz7UR7OUu+THQJOwdQXxmS/g8v0KLoFhv97HreycOrmAbqjXD/4VG2j0uMQ==} + engines: {node: '>=14'} + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.1 + safe-regex2: 2.0.0 + dev: false + + /find-up/5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /firebase/9.17.1: + resolution: {integrity: sha512-MSZaTRaaRLgDFLqoEnoPYK8zkLwQNvYeLZ3YSKdcQxG8hDifNO22ywS1cSA1ZCGHlQeOsDtfDwBejKcANf/RQw==} + dependencies: + '@firebase/analytics': 0.9.3_@firebase+app@0.9.3 + '@firebase/analytics-compat': 0.2.3_5qoz5voa527ucsvtk67mpz25ru + '@firebase/app': 0.9.3 + '@firebase/app-check': 0.6.3_@firebase+app@0.9.3 + '@firebase/app-check-compat': 0.3.3_5qoz5voa527ucsvtk67mpz25ru + '@firebase/app-compat': 0.2.3 + '@firebase/app-types': 0.9.0 + '@firebase/auth': 0.21.3_@firebase+app@0.9.3 + '@firebase/auth-compat': 0.3.3_2nquphbz2k7fjevr4yz5c7cjmi + '@firebase/database': 0.14.3 + '@firebase/database-compat': 0.3.3 + '@firebase/firestore': 3.8.3_@firebase+app@0.9.3 + '@firebase/firestore-compat': 0.3.3_2nquphbz2k7fjevr4yz5c7cjmi + '@firebase/functions': 0.9.3_@firebase+app@0.9.3 + '@firebase/functions-compat': 0.3.3_5qoz5voa527ucsvtk67mpz25ru + '@firebase/installations': 0.6.3_@firebase+app@0.9.3 + '@firebase/installations-compat': 0.2.3_2nquphbz2k7fjevr4yz5c7cjmi + '@firebase/messaging': 0.12.3_@firebase+app@0.9.3 + '@firebase/messaging-compat': 0.2.3_5qoz5voa527ucsvtk67mpz25ru + '@firebase/performance': 0.6.3_@firebase+app@0.9.3 + '@firebase/performance-compat': 0.2.3_5qoz5voa527ucsvtk67mpz25ru + '@firebase/remote-config': 0.4.3_@firebase+app@0.9.3 + '@firebase/remote-config-compat': 0.2.3_5qoz5voa527ucsvtk67mpz25ru + '@firebase/storage': 0.11.1_@firebase+app@0.9.3 + '@firebase/storage-compat': 0.3.1_2nquphbz2k7fjevr4yz5c7cjmi + '@firebase/util': 1.9.2 + transitivePeerDependencies: + - encoding + dev: false + + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flatted/3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + + /forwarded/0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: false + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob/8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: false + + /globals/13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globby/11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /http-errors/2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /http-parser-js/0.5.8: + resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + dev: false + + /idb/7.0.1: + resolution: {integrity: sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==} + dev: false + + /ieee754/1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + + /ignore/5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ipaddr.js/1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: false + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-path-inside/3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /js-sdsl/4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + dev: true + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-schema-traverse/1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: false + + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /light-my-request/5.9.1: + resolution: {integrity: sha512-UT7pUk8jNCR1wR7w3iWfIjx32DiB2f3hFdQSOwy3/EPQ3n3VocyipUxcyRZR0ahoev+fky69uA+GejPa9KuHKg==} + dependencies: + cookie: 0.5.0 + process-warning: 2.1.0 + set-cookie-parser: 2.5.1 + dev: false + + /locate-path/6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.camelcase/4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: false + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /long/4.0.0: + resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + dev: false + + /long/5.2.1: + resolution: {integrity: sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==} + dev: false + + /lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mime/3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: false + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch/5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /natural-compare-lite/1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true + + /natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /node-fetch/2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + + /on-exit-leak-free/2.1.0: + resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} + dev: false + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + dev: true + + /p-limit/3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + + /p-locate/5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pino-abstract-transport/1.0.0: + resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} + dependencies: + readable-stream: 4.3.0 + split2: 4.1.0 + dev: false + + /pino-std-serializers/6.1.0: + resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} + dev: false + + /pino/8.10.0: + resolution: {integrity: sha512-ODfIe+giJtQGsvNAEj5/sHHpL3TFBg161JBH4W62Hc0l0PJjsDFD1R7meLI4PZ2aoHDJznxFNShkJcaG/qJToQ==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.1.2 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pino-std-serializers: 6.1.0 + process-warning: 2.1.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.2 + sonic-boom: 3.2.1 + thread-stream: 2.3.0 + dev: false + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /process-warning/2.1.0: + resolution: {integrity: sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==} + dev: false + + /process/0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: false + + /protobufjs/6.11.3: + resolution: {integrity: sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==} + hasBin: true + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/long': 4.0.2 + '@types/node': 18.14.0 + long: 4.0.0 + dev: false + + /protobufjs/7.2.2: + resolution: {integrity: sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 18.14.0 + long: 5.2.1 + dev: false + + /proxy-addr/2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + + /punycode/2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-format-unescaped/4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + dev: false + + /readable-stream/4.3.0: + resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + dev: false + + /real-require/0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + dev: false + + /regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true + + /require-directory/2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: false + + /require-from-string/2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: false + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /ret/0.2.2: + resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} + engines: {node: '>=4'} + dev: false + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + /rfdc/1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + dev: false + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /safe-regex2/2.0.0: + resolution: {integrity: sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==} + dependencies: + ret: 0.2.2 + dev: false + + /safe-stable-stringify/2.4.2: + resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} + engines: {node: '>=10'} + dev: false + + /secure-json-parse/2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + dev: false + + /semver/7.3.8: + resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /set-cookie-parser/2.5.1: + resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} + dev: false + + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /sonic-boom/3.2.1: + resolution: {integrity: sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==} + dependencies: + atomic-sleep: 1.0.0 + dev: false + + /split2/4.1.0: + resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} + engines: {node: '>= 10.x'} + dev: false + + /statuses/2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: false + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /thread-stream/2.3.0: + resolution: {integrity: sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==} + dependencies: + real-require: 0.2.0 + dev: false + + /tiny-lru/10.0.1: + resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==} + engines: {node: '>=6'} + dev: false + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /tr46/0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + + /ts-node/10.9.1_tncu2ai53lzgmizdedur7lbibe: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 18.14.0 + acorn: 8.8.2 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tslib/1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tslib/2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + dev: false + + /tsutils/3.21.0_typescript@4.9.5: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.9.5 + dev: true + + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /typescript/4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.0 + + /v8-compile-cache-lib/3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /webidl-conversions/3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /websocket-driver/0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + dependencies: + http-parser-js: 0.5.8 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + dev: false + + /websocket-extensions/0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + dev: false + + /whatwg-url/5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + dev: true + + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: false + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /y18n/5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: false + + /yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + /yargs-parser/20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: false + + /yargs/16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + dev: false + + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + + /yocto-queue/0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} diff --git a/api/src/firebase.ts b/api/src/firebase.ts new file mode 100644 index 0000000..8f9919b --- /dev/null +++ b/api/src/firebase.ts @@ -0,0 +1,112 @@ +import { collection, getDocs, Firestore, doc, getDoc, addDoc, deleteDoc, updateDoc } from 'firebase/firestore/lite'; +import { Record } from "./models/record" +import { RawData } from "./models/rawdb" +import { NewRecord } from "./models/new_record" + +export async function getUserRecord(db: Firestore, user: string, record: string): Promise { + const recordDoc = await getDoc(doc(db, `users`, user, "records", record)) + if (recordDoc.exists()) { + const d = recordDoc.data() as RawData + return { "programming_language": d['programming_language'].jazyk, id: recordDoc.id, date: new Date(d.date.seconds), "time_spent": d['time_spent'], rating: d.rating, description: d.descriptionRaw } + } + else { + return null; + } +} + +export async function getAllUserRecords(db: Firestore, user: string): Promise { + const userDoc = await getDoc(doc(db, `users`, user)) + if (!userDoc.exists()) return null; + const records = (await getDocs(collection(db, "users", user, "records"))) + const recordArr: Record[] = [] + records.forEach(r => { + const d = r.data() as RawData + recordArr.push({ + date: d.date.toDate(), + "time_spent": d['time_spent'], + "programming_language": d['programming_language'].jazyk, + rating: d.rating, + description: d.descriptionRaw, + id: r.id + }) + }) + return recordArr +} + +export async function createRecord(db: Firestore, user: string, data: NewRecord): Promise { + const userDoc = await getDoc(doc(db, "users", user)) + if (userDoc.exists()) { + const docRef = await addDoc(collection(db, "users", user, "records"), { ...data,"time_spentRaw":textToSec(data.time_spent), "description": null }) + return docRef.id; + } + else { + return null; + } +} + +export async function updateRecord(db: Firestore, user: string, id: string, data: NewRecord): Promise { + const userDoc = await getDoc(doc(db, "users", user)) + if (userDoc.exists()) { + const docRef = await getDoc(doc(db, "users", user, "records", id)) + if (!docRef.exists()) return false; + await updateDoc(docRef.ref, {...data,"time_spentRaw":textToSec(data.time_spent)}) + return true; + } + else { + return null; + } +} + +export async function deleteRecord(db: Firestore, user: string, rec: string): Promise { + const userDoc = await getDoc(doc(db, "users", user)) + if (!userDoc.exists()) return null; + const recordDoc = await getDoc(doc(db, "users", user, "records", rec)) + if (!recordDoc.exists()) return false + await deleteDoc(recordDoc.ref) + return true; +} + +function textToSec(vstup: string):number|undefined { + const regex = /(\d+) hodin(?: |y |a )(\d+) minut(?:$|a$|y$)/gm + let s:number|undefined = 0; + let m; + + while ((m = regex.exec(vstup)) !== null) { + // This is necessary to avoid infinite loops with zero-width matches + if (m.index === regex.lastIndex) { + regex.lastIndex++; + } + + // The result can be accessed through the `m`-variable. + let ok = true; + m.forEach((match, groupIndex) => { + try { + switch (groupIndex) { + case 1: + if(s == undefined){ + ok = false; + break; + } + s += parseInt(match) * 3600 + break; + case 2: + if(s == undefined){ + ok = false; + break; + } + s += parseInt(match) * 60 + break; + default: + break; + } + } catch (error) { + ok = false; + } + }); + if(!ok) { + s = undefined; + break; + } + } + return s; +} \ No newline at end of file diff --git a/api/src/index.ts b/api/src/index.ts new file mode 100644 index 0000000..09a9491 --- /dev/null +++ b/api/src/index.ts @@ -0,0 +1,234 @@ +import fastify from 'fastify' +import { initializeApp } from 'firebase/app'; +import { getFirestore } from 'firebase/firestore/lite'; +import { Record } from "./models/record" +import path from 'path'; +import { createRecord, deleteRecord, getAllUserRecords, getUserRecord, updateRecord } from './firebase'; +import { Params } from './models/params'; +import { fastifyStatic } from "@fastify/static" +import { NewRecord, NewRecordRaw } from './models/new_record'; + +/* + Copyright (C) 2022 Matyáš Caras a Richard Pavlikán + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +debugme().then(() => { + + // Zkontrolovat proměnné + ["FIREBASE_KEY", "FIREBASE_AUTH", "FIREBASE_ID", "FIREBASE_STORAGE", "FIREBASE_MESSAGING", "FIREBASE_APPID"].forEach(v => { + if (!Object.keys(process.env).includes(v)) { + throw new Error(`Chybí systémová proměnná '${v}'`) + } + }) + + const server = fastify() + server.register(fastifyStatic, { + root: path.join(__dirname, "public") + }) + + // Konfigurace pro napojení na Firebase + const firebaseConfig = { + + apiKey: process.env["FIREBASE_KEY"], + + authDomain: process.env["FIREBASE_AUTH"], + + projectId: process.env["FIREBASE_ID"], + + storageBucket: process.env["FIREBASE_STORAGE"], + + messagingSenderId: process.env["FIREBASE_MESSAGING"], + + appId: process.env["FIREBASE_APPID"] + + }; + + // Připojit se na Firebase + const firebaseApp = initializeApp(firebaseConfig); + const db = getFirestore(firebaseApp); + + // Registrovat routy + + server.get("/", (req, res) => { + res.sendFile("index.html") + }) + + // API routy + + // Získat jeden záznam uživatele ID + server.get('/users/:userid/records/:recordid', async (req, res) => { + + if ((req.params as Params).userid == "" || (req.params as Params).recordid == "") return res.status(400).type("application/json").send(JSON.stringify({ "error": "Parametry nesmí být prázdné", "status": "error" })) + const record: Record | null = await getUserRecord(db, (req.params as Params).userid as string, (req.params as Params).recordid as string) + if (!record) return res.status(404).type("application/json").send(JSON.stringify({ "error": "Uživatel neexistuje", "status": "error" })) + + return res.type("application/json").send(JSON.stringify(record)) + }) + + // Smazat jeden záznam + server.delete("/users/:userid/records/:recordid", async (req, res) => { + if ((req.params as Params).userid == "" || (req.params as Params).recordid == "") return res.status(400).type("application/json").send(JSON.stringify({ "error": "Parametry nesmí být prázdné", "status": "error" })) + const r = await deleteRecord(db, (req.params as Params).userid as string, (req.params as Params).recordid as string) + if (r == null) { + return res.status(404).type("application/json").send(JSON.stringify({ "status": "error", "message": "Uživatel neexistuje" })) + } + else if (r == false) { + return res.status(404).type("application/json").send(JSON.stringify({ "status": "error", "message": "Záznam neexistuje" })) + } + return res.status(200).type("application/json").send(JSON.stringify({ "status": "OK" })) + }) + + // Upravit jeden záznam + server.put("/users/:userid/records/:recordid", { + schema: { + body: { + type: 'object', + properties: { + "date": { type: 'string' }, + "time_spent": { type: 'string' }, + "programming_language": { type: 'string' }, + "description": { type: 'string' }, + "rating": { type: 'number' } + } + } + } + }, async (req, res) => { + if ((req.params as Params).userid == "" || (req.params as Params).recordid == "") return res.status(400).type("application/json").send(JSON.stringify({ "error": "Parametry nesmí být prázdné", "status": "error" })) + try { + const data = req.body as NewRecordRaw + if (data.rating > 5 || data.rating < 0) return res.status(400).type("application/json").send(JSON.stringify({ "error": "'rating' je mimo interval 0-5", "status": "error" })) + const regex = /(\d+) hodin(?: |y |a )(\d+) minut(?:$|a$|y$)/gm + if(!regex.test(data.time_spent)){ + return res.status(400).type("application/json").send(JSON.stringify({ "error": "time_spent není ve správném formátu", "status": "error" })) + } + const jazyky: { jazyk: string, barva: number }[] = [ + { "jazyk": "C#", "barva": 0xff8200f3 }, + { "jazyk": "JavaScript", "barva": 0xfffdd700 }, + { "jazyk": "Python", "barva": 0xff0080ee }, + { "jazyk": "PHP", "barva": 0xff00abff }, + { "jazyk": "C++", "barva": 0xff1626ff }, + { "jazyk": "Kotlin", "barva": 0xffe34b7c }, + { "jazyk": "Java", "barva": 0xfff58219 }, + { "jazyk": "Dart", "barva": 0xff40c4ff }, + { "jazyk": "F#", "barva": 0xff85ddf3 }, + { "jazyk": "Elixir", "barva": 0xff543465 }, + { "jazyk": "Carbon", "barva": 0xff606060 }, + + ]; + + const j: { jazyk: string, barva: number } = (jazyky.filter((v) => v.jazyk.toLowerCase() == data['programming_language'].toLowerCase()).length > 0) ? jazyky.filter((v) => v.jazyk == data['programming_language'])[0] : { "jazyk": data["programming_language"], "barva": 0xffffffff } + + const record: NewRecord = { + date: new Date(data.date), "programming_language": j, "time_spent": data['time_spent'], rating: data.rating, + descriptionRaw: data.description, programmer: (req.params as Params).userid as string + } + const r = await updateRecord(db, (req.params as Params).userid as string, (req.params as Params).recordid as string, record) + if (r == null) { + return res.status(404).type("application/json").send(JSON.stringify({ "status": "error", "message": "Uživatel neexistuje" })) + } + else if(r == false){ + return res.status(404).type("application/json").send(JSON.stringify({ "status": "error", "message": "Záznam neexistuje" })) + } + return res.status(200).type("application/json").send(JSON.stringify({ "status": "OK" })) + } catch (error) { + if (process.env["NODE_DEBUG"] == "true") console.log(error) + return res.status(400).type("application/json").send(JSON.stringify({ "error": "Zaslaná data nejsou v platném formátu JSON", "status": "error" })) + } + }) + + // Získat všechny záznamy uživatele + server.get("/users/:userid/records", async (req, res) => { + if ((req.params as Params).userid == "") return res.status(400).type("application/json").send(JSON.stringify({ "error": "Parametry nesmí být prázdné", "status": "error" })) + const r: Record[] | null = await getAllUserRecords(db, (req.params as Params).userid as string) + if (!r) return res.status(404).type("application/json").send(JSON.stringify({ "status": "error", "message": "Uživatel neexistuje" })) + + return res.type("application/json").send(JSON.stringify( + r + )) + }) + + // Vytvořit nový záznam + server.post("/users/:userid/records", { + schema: { + body: { + type: 'object', + properties: { + "date": { type: 'string' }, + "time_spent": { type: 'string' }, + "programming_language": { type: 'string' }, + "description": { type: 'string' }, + "rating": { type: 'number' } + } + } + } + }, async (req, res) => { + if ((req.params as Params).userid == "") return res.status(400).type("application/json").send(JSON.stringify({ "error": "Parametry nesmí být prázdné", "status": "error" })) + try { + const data = req.body as NewRecordRaw + if (data.rating > 5 || data.rating < 0) return res.status(400).type("application/json").send(JSON.stringify({ "error": "'rating' je mimo interval 0-5", "status": "error" })) + const regex = /(\d+) hodin(?: |y |a )(\d+) minut(?:$|a$|y$)/gm + if(!regex.test(data.time_spent)){ + return res.status(400).type("application/json").send(JSON.stringify({ "error": "time_spent není ve správném formátu", "status": "error" })) + } + const jazyky: { jazyk: string, barva: number }[] = [ + { "jazyk": "C#", "barva": 0xff8200f3 }, + { "jazyk": "JavaScript", "barva": 0xfffdd700 }, + { "jazyk": "Python", "barva": 0xff0080ee }, + { "jazyk": "PHP", "barva": 0xff00abff }, + { "jazyk": "C++", "barva": 0xff1626ff }, + { "jazyk": "Kotlin", "barva": 0xffe34b7c }, + { "jazyk": "Java", "barva": 0xfff58219 }, + { "jazyk": "Dart", "barva": 0xff40c4ff }, + { "jazyk": "F#", "barva": 0xff85ddf3 }, + { "jazyk": "Elixir", "barva": 0xff543465 }, + { "jazyk": "Carbon", "barva": 0xff606060 }, + + ]; + + const j: { jazyk: string, barva: number } = (jazyky.filter((v) => v.jazyk.toLowerCase() == data['programming_language'].toLowerCase()).length > 0) ? jazyky.filter((v) => v.jazyk == data['programming_language'])[0] : { "jazyk": data["programming_language"], "barva": 0xffffffff } + + const record: NewRecord = { + date: new Date(data.date), "programming_language": j, "time_spent": data['time_spent'], rating: data.rating, + descriptionRaw: data.description, programmer: (req.params as Params).userid as string + } + const r:string|null = await createRecord(db, (req.params as Params).userid as string, record) + if (r == null) { + return res.status(404).type("application/json").send(JSON.stringify({ "status": "error", "message": "Uživatel neexistuje" })) + } + return res.status(201).type("application/json").send(JSON.stringify({"date":record.date,"programming_language":record.programming_language.jazyk,"time_spent":record.time_spent,rating:record.rating,description:record.descriptionRaw,id:r} as Record)) + } catch (error) { + if (process.env["NODE_DEBUG"] == "true") console.log(error) + return res.status(400).type("application/json").send(JSON.stringify({ "error": "Zaslaná data nejsou v platném formátu JSON", "status": "error" })) + } + }) + + server.listen({ port: (!process.env["PORT"]) ? 8080 : parseInt(process.env["PORT"]), host: "0.0.0.0" }, (err, address) => { + if (err) { + console.error(err) + process.exit(1) + } + console.log(`Server listening at ${address}`) + }) +}) + + +async function debugme() { + if (process.env["NODE_DEBUG"] == "true") { + const dotenv = await import("dotenv"); + dotenv.config() + } + return true; +} \ No newline at end of file diff --git a/api/src/models/new_record.ts b/api/src/models/new_record.ts new file mode 100644 index 0000000..c1f4402 --- /dev/null +++ b/api/src/models/new_record.ts @@ -0,0 +1,16 @@ +export type NewRecord={ + date:Date, + "time_spent":string, + "programming_language":{jazyk:string,barva:number}, + rating:number, + descriptionRaw:string, + programmer:string +} + +export type NewRecordRaw={ + date:string, + "time_spent":string, + "programming_language":string, + rating:number, + description:string +} \ No newline at end of file diff --git a/api/src/models/params.ts b/api/src/models/params.ts new file mode 100644 index 0000000..7388834 --- /dev/null +++ b/api/src/models/params.ts @@ -0,0 +1,4 @@ +export type Params={ + userid?:string, + recordid?:string +} \ No newline at end of file diff --git a/api/src/models/rawdb.ts b/api/src/models/rawdb.ts new file mode 100644 index 0000000..4d22c9b --- /dev/null +++ b/api/src/models/rawdb.ts @@ -0,0 +1,12 @@ +import { Timestamp } from "firebase/firestore/lite" + +export type RawData = { + "programming_language": {jazyk:string,barva:number}, + rating:number, + descriptionRaw: string, + description:unknown[], + date:Timestamp, + programmer:string, + toDate:Timestamp, + "time_spent":string +} \ No newline at end of file diff --git a/api/src/models/record.ts b/api/src/models/record.ts new file mode 100644 index 0000000..4c2f189 --- /dev/null +++ b/api/src/models/record.ts @@ -0,0 +1,8 @@ +export type Record={ + date:Date, + "time_spent":string, + "programming_language":string, + rating:number, + description:string, + id:string +} \ No newline at end of file diff --git a/api/src/public/.gitkeep b/api/src/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/tsconfig.json b/api/tsconfig.json new file mode 100644 index 0000000..75e9a25 --- /dev/null +++ b/api/tsconfig.json @@ -0,0 +1,106 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "CommonJS", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./build", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "src/*", + ] +} diff --git a/lib/main.dart b/lib/main.dart index 04f0eb7..0c0f79f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; -import 'firebase_options.dart'; //TODO: Přidejte si vlastní firebase nastavení +import 'firebase_options.dart'; /* Copyright (C) 2022 Matyáš Caras a Richard Pavlikán @@ -27,8 +27,7 @@ import 'firebase_options.dart'; //TODO: Přidejte si vlastní firebase nastaven void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( - options: DefaultFirebaseOptions - .currentPlatform, //TODO: Přidejte si vlastní firebase nastavení + options: DefaultFirebaseOptions.currentPlatform, ); runApp(const MyApp()); } @@ -40,7 +39,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return ResponsiveSizer( builder: (p0, p1, p2) => MaterialApp( - title: 'Deník Programátora', + title: 'Kodelog', theme: ThemeData( primarySwatch: Colors.blue, scaffoldBackgroundColor: Vzhled.backgroundColor, diff --git a/lib/okna/all_records.dart b/lib/okna/all_records.dart index bd4a208..1701048 100644 --- a/lib/okna/all_records.dart +++ b/lib/okna/all_records.dart @@ -2,40 +2,20 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:denikprogramatora/okna/app.dart'; import 'package:denikprogramatora/okna/settings.dart'; import 'package:denikprogramatora/okna/signin_page.dart'; +import 'package:denikprogramatora/utils/datum_cas.dart'; import 'package:denikprogramatora/utils/devicecontainer.dart'; -import 'package:denikprogramatora/utils/input_decoration.dart'; import 'package:denikprogramatora/utils/loading_widget.dart'; import 'package:denikprogramatora/utils/months.dart'; -import 'package:denikprogramatora/utils/my_category.dart'; import 'package:denikprogramatora/utils/my_container.dart'; import 'package:denikprogramatora/utils/new_record_dialog.dart'; -import 'package:denikprogramatora/utils/programmer.dart'; import 'package:denikprogramatora/utils/show_info_dialog.dart'; import 'package:denikprogramatora/utils/vzhled.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; import 'package:url_launcher/url_launcher_string.dart'; -/* - Copyright (C) 2022 Matyáš Caras a Richard Pavlikán - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - class AllRecordsPage extends StatefulWidget { const AllRecordsPage({super.key}); @@ -49,30 +29,7 @@ class _AllRecordsPageState extends State { int selectedDay = DateTime.now().day; int year = DateTime.now().year; - List categories = [MyCategory("Nic", "nic")]; - List programmers = [ - const Programmer("Nic", "nic"), - Programmer(name, userUid) - ]; - List filterJazyky = [ - {"jazyk": "Nic", "barva": 0xff8200f3}, - ]; - - late String selectedCategory; - late String selectedProgrammer; bool newestToOldest = true; - late String selectedJazyk; - DateTime fromDate = - DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day); - DateTime toDate = - DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day); - - bool searchByFromDate = false; - bool searchByToDate = false; - - int timeHour = 0; - int timeMinute = 0; - int review = 0; @override void initState() { @@ -86,29 +43,14 @@ class _AllRecordsPageState extends State { return; } - name = FirebaseAuth.instance.currentUser!.displayName!; - - ref.collection("programmers").get().then((value) { - for (var snap in value.docs) { - var data = snap.data(); - - programmers.add(Programmer(data["name"], snap.id)); - } - }); - - ref.collection("categories").get().then((value) { - for (var snap in value.docs) { - var data = snap.data(); - categories.add(MyCategory(data["name"], snap.id)); - } + ref.get().then((value) { + setState(() { + name = FirebaseAuth.instance.currentUser!.displayName ?? + value[ + "name"]; // fallback když uživatel je vytvořen skrz firebase admin + }); }); - filterJazyky.addAll(jazyky); - - selectedCategory = categories[0].id; - selectedProgrammer = programmers[0].id; - selectedJazyk = "Nic"; - mesic = months[DateTime.now().month - 1]; setState(() { @@ -148,7 +90,7 @@ class _AllRecordsPageState extends State { onPressed: () => showAboutDialog( context: context, applicationName: "Kodelog", - applicationVersion: "1.1.0", + applicationVersion: "2.0.1", applicationLegalese: "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", children: [ @@ -248,465 +190,115 @@ class _AllRecordsPageState extends State { Expanded( child: MyContainer( width: 90.w, - child: DeviceContainer( - mainAxisAlignmentDesktop: - MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - width: - (Device.screenType == ScreenType.mobile) - ? 80.w - : 40.w, - child: Column( + child: Center( + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.center, children: [ - const Text("Filtr", - style: Vzhled.nadpis), - const SizedBox(height: 15), - Row( - children: [ - Flexible( - child: Text( - "Záznamy seřazené od ${newestToOldest ? "nejnovějších po nejstarší" : "nejstarších po nejnovější"}"), - ), - TextButton( - onPressed: () { - setState(() { - newestToOldest = - !newestToOldest; - }); - }, - child: const Text( - "Změnit", - style: Vzhled.textBtn, - ), - ) - ], - ), - const SizedBox(height: 15), - DeviceContainer( - children: [ - const Text("Kategorie"), - const SizedBox(width: 15), - DropdownButton( - value: selectedCategory, - items: categories.map((e) { - return DropdownMenuItem( - value: e.id, - child: Text(e.name)); - }).toList(), - onChanged: (value) { - setState(() { - selectedCategory = value!; - }); - }, - ), - ], - ), - const SizedBox(height: 15), - DeviceContainer( - children: [ - const Text("Jazyk"), - const SizedBox(width: 15), - DropdownButton( - value: selectedJazyk, - dropdownColor: - Vzhled.backgroundColor, - items: filterJazyky - .map( - (e) => DropdownMenuItem( - value: e["jazyk"], - child: Text(e["jazyk"]), - ), - ) - .toList(), - onChanged: (value) { - setState(() { - selectedJazyk = - (value as String?)!; - }); - }, - ), - ], - ), - const SizedBox(height: 15), - DeviceContainer( - children: [ - const Text("Programátor"), - const SizedBox(width: 15), - DropdownButton( - value: selectedProgrammer, - items: programmers.map((e) { - return DropdownMenuItem( - value: e.id, - child: Text(e.name)); - }).toList(), - onChanged: (value) { - setState(() { - selectedProgrammer = value!; - }); - }, - ), - ], - ), - const SizedBox(height: 15), - DeviceContainer( - children: [ - const Text("Strávený čas"), - const SizedBox(width: 15), - SizedBox( - width: 75, - child: TextField( - decoration: - inputDecoration("Hodin"), - onChanged: (value) { - setState(() { - timeHour = - value.trim().isEmpty - ? 0 - : int.parse(value); - }); - }, - keyboardType: - TextInputType.number, - inputFormatters: < - TextInputFormatter>[ - FilteringTextInputFormatter - .digitsOnly - ], - ), - ), - const SizedBox(width: 15), - SizedBox( - width: 75, - child: TextField( - decoration: - inputDecoration("Minut"), - onChanged: (value) { - setState(() { - timeMinute = - value.trim().isEmpty - ? 0 - : int.parse(value); - }); - }, - keyboardType: - TextInputType.number, - inputFormatters: < - TextInputFormatter>[ - FilteringTextInputFormatter - .digitsOnly - ], - ), - ), - const SizedBox(width: 15), - if (timeMinute != 0 || - timeHour != 0) - TextButton( - onPressed: () { - setState(() { - timeMinute = 0; - timeHour = 0; - }); - }, - child: const Text( - "Zrušit filtr", - style: Vzhled.textBtn, - ), - ) - ], - ), - const SizedBox(height: 15), - DeviceContainer( - children: [ - const Text("Hodnocení"), - const SizedBox(width: 15), - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: List.generate( - 5, - (index) { - return IconButton( - onPressed: () { - setState(() { - review = index + 1; - }); - }, - icon: Icon(Icons.star, - color: (index + 1) <= - review - ? Colors.yellow - : Colors.grey), - ); - }, - ), - ), - if (review != 0) - TextButton( - onPressed: () { - setState(() { - review = 0; - }); - }, - child: const Text( - "Zrušit filtr", - style: Vzhled.textBtn, - ), - ) - ], - ), - const SizedBox(height: 15), - Row( - children: [ - const Text("Od: "), - const SizedBox(width: 15), - TextButton( - onPressed: () { - showDatePicker( - context: context, - initialDate: fromDate, - firstDate: DateTime( - DateTime.now() - .year - - 5), - lastDate: DateTime( - DateTime.now() - .year + - 5)) - .then((value) { - setState(() { - fromDate = value!; - searchByFromDate = true; - }); - }).onError( - (error, stackTrace) => - null); - }, - child: Text(searchByFromDate - ? "${fromDate.day}.${fromDate.month}.${fromDate.year}" - : "Vybrat den"), - ), - const SizedBox(width: 15), - if (searchByFromDate) - TextButton( - onPressed: () { - setState(() { - searchByFromDate = false; - }); - }, - child: const Text( - ("Zrušit filtr"), - style: Vzhled.textBtn, - ), - ), - ], - ), - const SizedBox(height: 5), - Row( - children: [ - const Text("Do: "), - const SizedBox(width: 15), - TextButton( - onPressed: () { - showDatePicker( - context: context, - initialDate: toDate, - firstDate: DateTime( - DateTime.now() - .year - - 5), - lastDate: DateTime( - DateTime.now() - .year + - 5)) - .then((value) { - setState(() { - toDate = value!; - searchByToDate = true; - }); - }).onError( - (error, stackTrace) => - null); - }, - child: Text(searchByToDate - ? "${toDate.day}.${toDate.month}.${toDate.year}" - : "Vybrat den"), - ), - const SizedBox(width: 15), - if (searchByToDate) - TextButton( - onPressed: () { - setState(() { - searchByToDate = false; - }); - }, - child: const Text( - ("Zrušit filtr"), - style: Vzhled.textBtn, - ), - ), - ], - ), + Text( + "Záznamy seřazené \nod ${newestToOldest ? "nejnovějších po nejstarší" : "nejstarších po nejnovější"}"), + TextButton( + onPressed: () { + setState(() { + newestToOldest = !newestToOldest; + }); + }, + child: const Text( + "Změnit", + style: Vzhled.textBtn, + ), + ) ], ), - ), - SizedBox( - width: - (Device.screenType == ScreenType.mobile) - ? 80.w - : 40.w, - child: StreamBuilder( - stream: ref - .collection("records") - .orderBy("fromDate", - descending: newestToOldest) - .snapshots(), - builder: (context, snapshot) { - if (snapshot.hasData) { - var docs = snapshot.data!.docs; - - if (selectedProgrammer != "nic") { - docs = docs - .where((element) => - element - .data()["programmer"] == - selectedProgrammer) - .toList(); - } - - if (selectedCategory != "nic") { - docs = docs - .where((element) => (element - .data()[ - "categories"] as List) - .contains(selectedCategory)) - .toList(); - } - - if (selectedJazyk != "Nic") { - docs = docs - .where((element) => - element.data()["language"] - ["jazyk"] == - selectedJazyk) - .toList(); - } - - if (searchByFromDate) { - docs = docs - .where((d) => - (d.data()["fromDate"] - as Timestamp) - .toDate() - .compareTo( - fromDate) == - 1 || - (d.data()["fromDate"] - as Timestamp) - .toDate() - .compareTo( - fromDate) == - 0) - .toList(); - } - - if (searchByToDate) { - docs = docs - .where((d) => - (d.data()["toDate"] - as Timestamp) - .toDate() - .compareTo( - toDate) == - -1 || - (d.data()["toDate"] - as Timestamp) - .toDate() - .compareTo( - toDate) == - 0) - .toList(); - } - - if (timeHour != 0 || - timeMinute != 0) { - if (kDebugMode) { - print( - "${timeHour == 0 ? "" : (timeHour == 1 ? "$timeHour hodina" : "$timeHour hodin")}${timeMinute == 0 ? "" : (timeMinute == 1 ? "a $timeMinute minuta" : " a $timeMinute minut")}"); - } - docs = docs - .where((element) => (element - .data()["codingTime"] == - "${timeHour == 0 ? "" : (timeHour == 1 ? "$timeHour hodina" : "$timeHour hodin")}${timeMinute == 0 ? "" : (timeMinute == 1 ? "a $timeMinute minuta" : " a $timeMinute minut")}")) - .toList(); - } - - if (review != 0) { - docs = docs - .where((element) => - element.data()["review"] == - review) - .toList(); - } + const SizedBox(height: 5), + SizedBox( + width: (Device.screenType == + ScreenType.mobile) + ? 80.w + : 40.w, + child: StreamBuilder( + stream: ref + .collection("records") + .orderBy("date", + descending: newestToOldest) + .snapshots(), + builder: (context, snapshot) { + if (snapshot.hasData) { + var docs = snapshot.data!.docs; - return Column( - children: List.generate( - docs.length, - (index) { - var data = docs[index].data(); + return Column( + children: List.generate( + docs.length, + (index) { + var data = docs[index].data(); - return Padding( - padding: - const EdgeInsets.all(8.0), - child: Container( - decoration: BoxDecoration( - borderRadius: - const BorderRadius - .all( - Radius.circular(4), + return Padding( + padding: + const EdgeInsets.all( + 8.0), + child: Container( + decoration: BoxDecoration( + borderRadius: + const BorderRadius + .all( + Radius.circular(4), + ), + color: Color(data[ + "programming_language"] + ["barva"]), ), - color: Color( - data["language"] - ["barva"]), - ), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => - showInfoDialog( - context, - data, - docs[index].id), - child: Padding( - padding: - const EdgeInsets - .all(8.0), - child: Row( - children: [ - Text( - "${(data["fromDate"] as Timestamp).toDate().year}.${(data["fromDate"] as Timestamp).toDate().month}.${(data["fromDate"] as Timestamp).toDate().day} ${(data["fromDate"] as Timestamp).toDate().hour < 10 ? "0${(data["fromDate"] as Timestamp).toDate().hour}" : (data["fromDate"] as Timestamp).toDate().hour}:${(data["fromDate"] as Timestamp).toDate().minute < 10 ? "0${(data["fromDate"] as Timestamp).toDate().minute}" : (data["fromDate"] as Timestamp).toDate().minute}"), - const SizedBox( - width: 20, - ), - Text( - " - ${data["language"]["jazyk"]}") - ], + child: Material( + color: + Colors.transparent, + child: InkWell( + onTap: () => + showInfoDialog( + context, + data, + docs[index] + .id, + name), + child: Padding( + padding: + const EdgeInsets + .all(8.0), + child: Row( + children: [ + Text((data["date"] + as Timestamp) + .toDate() + .dateString), + const SizedBox( + width: 20, + ), + Text( + "${data["programming_language"]["jazyk"]}", + style: const TextStyle( + fontWeight: + FontWeight + .bold), + ), + Text( + " - ${data["time_spent"]}") + ], + ), ), ), ), ), - ), - ); - }, - ), - ); - } - return const LoadingWidget(); - }, + ); + }, + ), + ); + } + return const LoadingWidget(); + }, + ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/okna/app.dart b/lib/okna/app.dart index 095e49d..eb1db74 100644 --- a/lib/okna/app.dart +++ b/lib/okna/app.dart @@ -66,10 +66,15 @@ class _HlavniOknoState extends State { (route) => false); return; } - userUid = FirebaseAuth.instance.currentUser!.uid; - name = FirebaseAuth.instance.currentUser!.displayName!; + ref.get().then((value) { + setState(() { + name = FirebaseAuth.instance.currentUser!.displayName ?? + value[ + "name"]; // fallback když uživatel je vytvořen skrz firebase admin + }); + }); mesic = months[DateTime.now().month - 1]; setState(() { @@ -108,9 +113,9 @@ class _HlavniOknoState extends State { onPressed: () => showAboutDialog( context: context, applicationName: "Kodelog", - applicationVersion: "1.1.0", + applicationVersion: "2.0.1", applicationLegalese: - "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", + "©️ 2023 Matyáš Caras a Richard Pavlikán" /*+",\n vydáno pod licencí AGPLv3"*/, children: [ TextButton( child: const Text("Zdrojový kód"), @@ -144,8 +149,8 @@ class _HlavniOknoState extends State { ), MyContainer( width: (Device.screenType == ScreenType.mobile) - ? 95.w - : 45.w, + ? 90.w + : 40.w, child: DeviceContainer( mainAxisAlignmentDesktop: MainAxisAlignment.spaceBetween, @@ -220,19 +225,12 @@ class _HlavniOknoState extends State { if (snapshot.hasData) { var docs = snapshot.data!.docs; var jenMesic = docs - .where((d) => - DateTime.parse( - "$year-${(mesic.position + 1 < 10) ? "0${mesic.position + 1}" : mesic.position + 1}-${selectedDay < 10 ? "0$selectedDay" : selectedDay} 00:00:00") - .isBefore( - (d.data()["toDate"] - as Timestamp) - .toDate()) && - DateTime.parse( - "$year-${(mesic.position + 1 < 10) ? "0${mesic.position + 1}" : mesic.position + 1}-${selectedDay < 10 ? "0$selectedDay" : selectedDay} 23:59:59") - .isAfter( - (d.data()["fromDate"] - as Timestamp) - .toDate())) + .where((d) => DateTime.parse( + "$year-${(mesic.position + 1 < 10) ? "0${mesic.position + 1}" : mesic.position + 1}-${selectedDay < 10 ? "0$selectedDay" : selectedDay} 00:00:00") + .isAtSameMomentAs( + (d.data()["date"] + as Timestamp) + .toDate())) .toList() // vybere pouze záznamy, které probíhají ve vybraný den ..sort( razeni[vybraneRazeni], @@ -262,9 +260,9 @@ class _HlavniOknoState extends State { .all( Radius.circular(4), ), - color: Color( - data["language"] - ["barva"]), + color: Color(data[ + "programming_language"] + ["barva"]), ), child: Material( color: Colors.transparent, @@ -274,7 +272,8 @@ class _HlavniOknoState extends State { context, data, jenMesic[index] - .id), + .id, + name), child: Padding( padding: const EdgeInsets @@ -282,12 +281,14 @@ class _HlavniOknoState extends State { child: Row( children: [ Text( - "${(data["fromDate"] as Timestamp).toDate().hour < 10 ? "0${(data["fromDate"] as Timestamp).toDate().hour}" : (data["fromDate"] as Timestamp).toDate().hour}:${(data["fromDate"] as Timestamp).toDate().minute < 10 ? "0${(data["fromDate"] as Timestamp).toDate().minute}" : (data["fromDate"] as Timestamp).toDate().minute}"), - const SizedBox( - width: 20, + "${data["programming_language"]["jazyk"]}", + style: const TextStyle( + fontWeight: + FontWeight + .bold), ), Text( - " - ${data["language"]["jazyk"]}") + " - ${data["time_spent"]}") ], ), ), @@ -308,7 +309,10 @@ class _HlavniOknoState extends State { height: 50, ), SizedBox( - width: 45.w, + width: + (Device.screenType == ScreenType.mobile) + ? 60.w + : 40.w, child: Column( children: [ DeviceContainer( @@ -504,9 +508,10 @@ class _HlavniOknoState extends State { : 5), (index) { return Row( - mainAxisAlignment: (Device.screenType == ScreenType.mobile) - ? MainAxisAlignment.center - : MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + // (Device.screenType == ScreenType.mobile) + // ? MainAxisAlignment.center + // : MainAxisAlignment.start, children: List.generate( (Device.screenType == ScreenType.mobile) ? 3 : 7, (index) { diff --git a/lib/okna/settings.dart b/lib/okna/settings.dart index d7ac392..c1420dd 100644 --- a/lib/okna/settings.dart +++ b/lib/okna/settings.dart @@ -1,34 +1,21 @@ import 'package:denikprogramatora/okna/app.dart'; import 'package:denikprogramatora/okna/signin_page.dart'; +import 'package:denikprogramatora/okna/users_page.dart'; import 'package:denikprogramatora/utils/devicecontainer.dart'; import 'package:denikprogramatora/utils/loading_widget.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; import 'package:url_launcher/url_launcher_string.dart'; +// ignore: avoid_web_libraries_in_flutter +import 'dart:html' as html; +import '../utils/csv.dart'; import '../utils/my_container.dart'; import '../utils/new_record_dialog.dart'; import '../utils/vzhled.dart'; import 'all_records.dart'; -/* - Copyright (C) 2022 Matyáš Caras a Richard Pavlikán - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - class NastaveniOkno extends StatefulWidget { const NastaveniOkno({super.key}); @@ -39,6 +26,8 @@ class NastaveniOkno extends StatefulWidget { class _NastaveniOknoState extends State { var _loading = true; var name = "error"; + bool isAdmin = false; + @override void initState() { super.initState(); @@ -49,7 +38,16 @@ class _NastaveniOknoState extends State { (route) => false); return; } - name = FirebaseAuth.instance.currentUser!.displayName!; + + ref.get().then((value) { + setState(() { + name = FirebaseAuth.instance.currentUser!.displayName ?? + value[ + "name"]; // fallback když uživatel je vytvořen skrz firebase admin + + isAdmin = value["isAdmin"]; + }); + }); setState(() { _loading = false; @@ -60,247 +58,380 @@ class _NastaveniOknoState extends State { Widget build(BuildContext context) { return Scaffold( body: SingleChildScrollView( - child: Center( - child: SizedBox( - width: 90.w, - height: 100.h, - child: (_loading) - ? const LoadingWidget() - : Column(children: [ - DeviceContainer( - mainAxisAlignmentDesktop: MainAxisAlignment.spaceBetween, + child: Center( + child: SizedBox( + width: 90.w, + height: 100.h, + child: (_loading) + ? const LoadingWidget() + : Column( children: [ - MyContainer( - width: (Device.screenType == ScreenType.mobile) - ? 90.w - : 35.w, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (name != "error") Text("Ahoj $name"), - TextButton( - onPressed: () => showAboutDialog( - context: context, - applicationName: "Kodelog", - applicationVersion: "1.1.0", - applicationLegalese: - "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", - children: [ - TextButton( - child: const Text("Zdrojový kód"), - onPressed: () => launchUrlString( - "https://github.com/Royal-Buccaneers/kodelog"), - ) - ]), - child: const Text( - "Licence", - style: Vzhled.textBtn, - ), - ), - TextButton( - onPressed: () async { - await FirebaseAuth.instance.signOut(); - if (!mounted) return; - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (c) => const SignInPage()), - (route) => false); - }, - child: const Text( - "Odhlásit se", - style: Vzhled.textBtn, - ), - ) - ], - ), - ), - MyContainer( - width: (Device.screenType == ScreenType.mobile) - ? 90.w - : 40.w, - child: DeviceContainer( - mainAxisAlignmentDesktop: - MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () => Navigator.of(context) - .pushReplacement(MaterialPageRoute( - builder: (context) => - const HlavniOkno())), - child: const Text( - "Denní přehled", - style: TextStyle(color: Vzhled.textColor), - ), - ), - const SizedBox(height: 5), - TextButton( - onPressed: () { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => - const AllRecordsPage())); - }, - child: const Text( - "Všechny\nzáznamy", - style: TextStyle(color: Vzhled.textColor), - ), - ), - const SizedBox(height: 5), - TextButton( - onPressed: () {}, - child: const Text( - "Nastavení", - style: TextStyle( - color: Vzhled.textColor, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(height: 5), - OutlinedButton( - onPressed: () => showCreateItemDialog(context), - style: Vzhled.orangeCudlik, - child: const Text( - "Přidat záznam", - ), - ) - ], - ), - ), - ], - ), - const SizedBox(height: 5), - Expanded( - child: MyContainer( - width: 90.w, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, + DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, children: [ - Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular( - 10, - ), - ), - color: Vzhled.purple, - ), - width: 400, - child: InkWell( - onTap: () => showProgrammersDialog(context, - jenMenit: true), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Text( - "Upravit programátory", - style: Vzhled.nadpis, - ) - ], + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 35.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (name != "error") Text("Ahoj $name"), + TextButton( + onPressed: () => showAboutDialog( + context: context, + applicationName: "Kodelog", + applicationVersion: "2.0.1", + applicationLegalese: + "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", + children: [ + TextButton( + child: const Text("Zdrojový kód"), + onPressed: () => launchUrlString( + "https://github.com/Royal-Buccaneers/kodelog"), + ) + ]), + child: const Text( + "Licence", + style: Vzhled.textBtn, + ), ), - ), + TextButton( + onPressed: () async { + await FirebaseAuth.instance.signOut(); + if (!mounted) return; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (c) => const SignInPage()), + (route) => false); + }, + child: const Text( + "Odhlásit se", + style: Vzhled.textBtn, + ), + ) + ], ), ), - const SizedBox( - height: 15, - ), - Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular( - 10, + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 40.w, + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () => Navigator.of(context) + .pushReplacement(MaterialPageRoute( + builder: (context) => + const HlavniOkno())), + child: const Text( + "Denní přehled", + style: TextStyle(color: Vzhled.textColor), ), ), - color: Vzhled.purple), - width: 400, - child: InkWell( - onTap: () => showCategoriesDialog(context, [], - jenMenit: true), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Text( - "Upravit kategorie", - style: Vzhled.nadpis, - ) - ], + const SizedBox(height: 5), + TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + const AllRecordsPage())); + }, + child: const Text( + "Všechny\nzáznamy", + style: TextStyle(color: Vzhled.textColor), + ), ), - ), + const SizedBox(height: 5), + TextButton( + onPressed: () {}, + child: const Text( + "Nastavení", + style: TextStyle( + color: Vzhled.textColor, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 5), + OutlinedButton( + onPressed: () => + showCreateItemDialog(context), + style: Vzhled.orangeCudlik, + child: const Text( + "Přidat záznam", + ), + ) + ], ), ), - const SizedBox( - height: 15, - ), - Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular( - 10, + ], + ), + const SizedBox(height: 5), + Expanded( + child: MyContainer( + width: 90.w, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + color: Vzhled.purple), + width: 400, + child: InkWell( + onTap: () => showEditJazyk(), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: const [ + Text( + "Oblíbený jazyk", + style: Vzhled.nadpis, + ) + ], + ), ), ), - color: Vzhled.purple), - width: 400, - child: InkWell( - onTap: () => showEditJazyk(), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Text( - "Oblíbený jazyk", - style: Vzhled.nadpis, - ) - ], + ), + const SizedBox( + height: 15, + ), + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + color: Vzhled.purple), + width: 400, + child: InkWell( + onTap: () => showCategoriesDialog(context, [], + jenMenit: true), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: const [ + Text( + "Upravit kategorie", + style: Vzhled.nadpis, + ) + ], + ), + ), ), ), - ), + const SizedBox( + height: 15, + ), + DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.center, + mainAxisAlignmentMobile: + MainAxisAlignment.center, + children: [ + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + color: Vzhled.purple), + width: 400, + child: InkWell( + onTap: () async { + var csv = await exportCsv(); + var blob = html.Blob([csv]); + var url = + html.Url.createObjectUrlFromBlob( + blob); + var anchor = html.document + .createElement('a') + as html.AnchorElement + ..href = url + ..style.display = 'none' + ..download = + 'db_${name.replaceAll(" ", "_")}.csv'; + html.document.body!.children + .add(anchor); + + anchor.click(); + + html.document.body!.children + .remove(anchor); + html.Url.revokeObjectUrl(url); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: const [ + Text( + "Exportovat CSV", + style: Vzhled.nadpis, + ) + ], + ), + ), + ), + ), + const SizedBox( + width: 10, + height: 10, + ), + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + color: Vzhled.purple), + width: 400, + child: InkWell( + onTap: () async { + try { + var p = await importCsv(name); + if (p != -1) { + if (!mounted) return; + showDialog( + context: context, + builder: (c) => AlertDialog( + title: const Text("Úspěch!"), + content: Text( + "Importováno $p záznamů"), + actions: [ + TextButton( + onPressed: () => + Navigator.of(c).pop(), + child: const Text("Ok")) + ], + ), + ); + } + } catch (e) { + showDialog( + context: context, + builder: (c) => AlertDialog( + title: const Text( + "Při importování nastala chyba!"), + content: Text(e.toString()), + ), + ); + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: const [ + Text( + "Importovat CSV", + style: Vzhled.nadpis, + ) + ], + ), + ), + ), + ), + ], + ), + const SizedBox( + height: 15, + ), + if (isAdmin) + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + color: Vzhled.purple), + width: 400, + child: InkWell( + onTap: () => Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const UsersPage(), + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: const [ + Text( + "Správa uživatelů", + style: Vzhled.nadpis, + ) + ], + ), + ), + ), + ), + ], ), - ]), - )) - ]), + ), + ) + ], + ), + ), ), - )), + ), ); } showEditJazyk() { showDialog( - context: context, - builder: (_) => AlertDialog( - title: const Text("Oblíbený jazyk", style: Vzhled.velkyText), - scrollable: true, - content: SizedBox( - width: 20.w, - child: StreamBuilder( - stream: ref.snapshots(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return DropdownButton( - value: snapshot.data!.data()!["favourite"], - dropdownColor: Vzhled.backgroundColor, - items: jazyky - .map( - (e) => DropdownMenuItem( - value: e["jazyk"], - child: SizedBox( - width: 17.w, child: Text(e["jazyk"])), - ), - ) - .toList(), - onChanged: (value) { - ref.update({"favourite": value!}); - }); - } - return const LoadingWidget(); - }), - ), - )); + context: context, + builder: (_) => AlertDialog( + title: const Text("Oblíbený jazyk", style: Vzhled.velkyText), + scrollable: true, + content: SizedBox( + width: 20.w, + child: StreamBuilder( + stream: ref.snapshots(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return DropdownButton( + value: snapshot.data!.data()!["favourite"], + dropdownColor: Vzhled.backgroundColor, + items: jazyky + .map( + (e) => DropdownMenuItem( + value: e["jazyk"], + child: SizedBox( + width: 17.w, child: Text(e["jazyk"])), + ), + ) + .toList(), + onChanged: (value) { + ref.update({"favourite": value!}); + }); + } + return const LoadingWidget(); + }), + ), + ), + ); } } diff --git a/lib/okna/signin_page.dart b/lib/okna/signin_page.dart index cdfbace..bc34204 100644 --- a/lib/okna/signin_page.dart +++ b/lib/okna/signin_page.dart @@ -9,23 +9,6 @@ import 'package:flutter/scheduler.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; import 'package:url_launcher/url_launcher_string.dart'; -/* - Copyright (C) 2022 Matyáš Caras a Richard Pavlikán - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - class SignInPage extends StatefulWidget { const SignInPage({super.key}); @@ -34,8 +17,10 @@ class SignInPage extends StatefulWidget { } class _SignInPageState extends State { - bool showSignIn = true; bool isLoading = true; + bool isSignInWidget = true; + bool showEmailPage = true; + String oldDocId = ""; TextEditingController emailCon = TextEditingController(); TextEditingController passwordCon = TextEditingController(); @@ -70,7 +55,7 @@ class _SignInPageState extends State { : Stack( children: [ Center( - child: showSignIn ? signInWidget() : registerWidget(), + child: isSignInWidget ? signInWidget() : registerWidget(), ), Positioned( bottom: 10, @@ -79,7 +64,7 @@ class _SignInPageState extends State { onPressed: () => showAboutDialog( context: context, applicationName: "Kodelog", - applicationVersion: "1.1.0", + applicationVersion: "2.0.1", applicationLegalese: "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", children: [ @@ -121,7 +106,7 @@ class _SignInPageState extends State { SizedBox( width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, child: TextFormField( - decoration: Vzhled.inputDecoration("E-mail"), + decoration: Vzhled.inputDecoration("E-mail nebo Username"), cursorColor: Vzhled.textColor, keyboardType: TextInputType.emailAddress, autocorrect: false, @@ -134,9 +119,6 @@ class _SignInPageState extends State { validator: (value) { if (value!.trim().isEmpty) { return "Toto pole je povinné!"; - } else if (!RegExp(r'[\w\.]+@[a-z0-9]+\.[a-z]{1,3}') - .hasMatch(value)) { - return "Neplatný e-mail!"; } return null; }, @@ -188,11 +170,15 @@ class _SignInPageState extends State { TextButton( onPressed: () { setState(() { - showSignIn = false; + isSignInWidget = false; + showEmailPage = true; }); }, - child: const Text("Registrovat se", style: Vzhled.textBtn), - ) + child: const Text( + "Přihlašuji se poprvé", + style: Vzhled.textBtn, + ), + ), ], ), ), @@ -201,7 +187,9 @@ class _SignInPageState extends State { Widget registerWidget() { GlobalKey form = GlobalKey(); + return MyContainer( + height: 70.h, width: (Device.screenType == ScreenType.mobile) ? 80.w : 40.w, child: Form( key: form, @@ -216,119 +204,142 @@ class _SignInPageState extends State { const SizedBox( height: 30, ), - SizedBox( - width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, - child: TextFormField( - decoration: Vzhled.inputDecoration("Jméno"), - cursorColor: Vzhled.textColor, - controller: nameCon, - onFieldSubmitted: (_) { - if (form.currentState!.validate()) { - signUp(); - } - }, - validator: (value) { - if (value!.trim().isEmpty) { - return "Toto pole je povinné!"; - } - return null; - }, - ), - ), - const SizedBox( - height: 20, - ), - SizedBox( - width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, - child: TextFormField( - decoration: Vzhled.inputDecoration("E-mail"), - cursorColor: Vzhled.textColor, - controller: emailCon, - keyboardType: TextInputType.emailAddress, - autocorrect: false, - onFieldSubmitted: (_) { - if (form.currentState!.validate()) { - signUp(); - } - }, - validator: (value) { - if (value!.trim().isEmpty) { - return "Toto pole je povinné!"; - } else if (!RegExp(r'[\w\.]+@[a-z0-9]+\.[a-z]{1,3}') - .hasMatch(value)) { - return "Neplatný e-mail!"; - } - return null; - }, - ), - ), - const SizedBox( - height: 20, - ), - SizedBox( - width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, - child: TextFormField( - decoration: Vzhled.inputDecoration("Heslo"), - cursorColor: Vzhled.textColor, - obscureText: true, - controller: passwordCon, - onFieldSubmitted: (_) { - if (form.currentState!.validate()) { - signUp(); - } - }, - validator: (value) { - if (value!.trim().isEmpty) { - return "Toto pole je povinné!"; - } - return null; - }, - ), - ), - const SizedBox( - height: 20, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - "Oblíbený jazyk:", - style: TextStyle( - fontWeight: FontWeight.bold, + showEmailPage + ? SizedBox( + width: + (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, + child: TextFormField( + decoration: Vzhled.inputDecoration("Váš e-mail"), + cursorColor: Vzhled.textColor, + keyboardType: TextInputType.emailAddress, + autocorrect: false, + controller: emailCon, + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + ), + ) + : SizedBox( + width: + (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, + child: TextFormField( + decoration: Vzhled.inputDecoration("Váš nové heslo"), + cursorColor: Vzhled.textColor, + autocorrect: false, + obscureText: true, + controller: passwordCon, + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + ), ), - ), - const SizedBox(width: 5), - DropdownButton( - value: jazyk, - dropdownColor: Vzhled.backgroundColor, - items: jazyky - .map( - (e) => DropdownMenuItem( - value: e["jazyk"], - child: Text(e["jazyk"]), - ), - ) - .toList(), - onChanged: (value) { - setState(() { - jazyk = (value as String?)!; - }); - }, - ), - ], - ), const SizedBox( height: 20, ), - OutlinedButton( - style: Vzhled.orangeCudlik, - onPressed: () async { - if (form.currentState!.validate()) { - signUp(); - } - }, - child: const Text("Registrovat se"), - ), + showEmailPage + ? OutlinedButton( + style: Vzhled.orangeCudlik, + onPressed: () async { + if (form.currentState!.validate()) { + var emailRef = await FirebaseFirestore.instance + .collection("users") + .where("email", isEqualTo: emailCon.text) + .get(); + + if (emailRef.docs.isEmpty) { + // ignore: use_build_context_synchronously + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text( + "Účet s tímto emailem neexistuje. Pro přístup do aplikace musí váš email přidat admin aplikace."), + ), + ); + return; + } + + setState(() { + oldDocId = emailRef.docs.first.id; + showEmailPage = false; + }); + } + }, + child: const Text("Pokračovat"), + ) + : OutlinedButton( + onPressed: () async { + if (form.currentState!.validate()) { + await FirebaseAuth.instance + .createUserWithEmailAndPassword( + email: emailCon.text, + password: passwordCon.text) + .then((value) async { + await FirebaseFirestore.instance + .collection("users") + .doc(oldDocId) + .get() + .then((value) { + FirebaseFirestore.instance + .collection("users") + .doc(FirebaseAuth.instance.currentUser!.uid) + .set({ + "name": value["name"], + "favourite": value["favourite"], + "isAdmin": value["isAdmin"], + "username": value["username"], + "email": value["email"], + }); + }).then((value) { + FirebaseFirestore.instance + .collection("users") + .doc(oldDocId) + .delete(); + }); + + // ignore: use_build_context_synchronously + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const HlavniOkno(), + ), + ); + }).onError((e, st) { + if (e + .toString() + .contains("firebase_auth/email-already-in-use")) { + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text( + "Váš účet už existuje, prosím přihlaste se"), + ), + ); + } else if (e + .toString() + .contains("firebase_auth/wrong-password")) { + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text("Zadáváte špatné heslo!"), + ), + ); + } + + return; + }); + } + }, + style: Vzhled.orangeCudlik, + child: const Text("Pokračovat")), const SizedBox( height: 10, ), @@ -339,21 +350,54 @@ class _SignInPageState extends State { TextButton( onPressed: () { setState(() { - showSignIn = true; + isSignInWidget = true; }); }, - child: const Text("Přihlásit se", style: Vzhled.textBtn), - ) + child: const Text( + "Účet mám vytvořen, chci se přihlásit", + style: Vzhled.textBtn, + ), + ), ], ), ), ); } - void signIn() { + void signIn() async { + setState(() { + isLoading = true; + }); + + String email = emailCon.text; + + if (!emailCon.text.contains("@")) { + var usernameRef = await FirebaseFirestore.instance + .collection("users") + .where("username", isEqualTo: emailCon.text) + .get(); + + if (usernameRef.docs.isEmpty) { + // ignore: use_build_context_synchronously + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text("Účet s tímto jménem neexistuje"), + ), + ); + + setState(() { + isLoading = false; + }); + return; + } + + email = usernameRef.docs.single.data()["email"]; + } + FirebaseAuth.instance - .signInWithEmailAndPassword( - email: emailCon.text, password: passwordCon.text) + .signInWithEmailAndPassword(email: email, password: passwordCon.text) .then( (value) => Navigator.pushReplacement( context, @@ -388,49 +432,11 @@ class _SignInPageState extends State { debugPrint(e.toString()); } }); - } - void signUp() { - FirebaseAuth.instance - .createUserWithEmailAndPassword( - email: emailCon.text, password: passwordCon.text) - .then( - (value) async { - await FirebaseFirestore.instance - .collection("users") - .doc(FirebaseAuth.instance.currentUser!.uid) - .set({ - "name": nameCon.text, - "email": emailCon.text, - "favourite": jazyk, - }); - value.user?.updateDisplayName(nameCon.text); - - if (!mounted) return; - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (c) => const HlavniOkno()), - ); - }, - ).onError((error, stackTrace) { - if (error.toString().contains("firebase_auth/email-already-in-use")) { - showDialog( - context: context, - builder: (c) => const AlertDialog( - title: Text("Chyba"), - content: Text("E-mail je již zaregistrovaný"), - ), - ); - } else { - showDialog( - context: context, - builder: (c) => const AlertDialog( - title: Text("Chyba"), - content: Text("Nastala neznámá chyba."), - ), - ); - debugPrint(error.toString()); - } - }); + if (mounted) { + setState(() { + isLoading = false; + }); + } } } diff --git a/lib/okna/users_page.dart b/lib/okna/users_page.dart new file mode 100644 index 0000000..16518fe --- /dev/null +++ b/lib/okna/users_page.dart @@ -0,0 +1,633 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:denikprogramatora/okna/all_records.dart'; +import 'package:denikprogramatora/okna/signin_page.dart'; +import 'package:denikprogramatora/utils/devicecontainer.dart'; +import 'package:denikprogramatora/utils/loading_widget.dart'; +import 'package:denikprogramatora/utils/my_container.dart'; +import 'package:denikprogramatora/utils/new_record_dialog.dart'; +import 'package:denikprogramatora/utils/really_delete.dart'; +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import 'app.dart'; + +class UsersPage extends StatefulWidget { + const UsersPage({super.key}); + + @override + State createState() => _UsersPageState(); +} + +class _UsersPageState extends State { + var _loading = true; + var name = "error"; + @override + void initState() { + super.initState(); + if (FirebaseAuth.instance.currentUser == null) { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (c) => const SignInPage()), + (route) => false); + return; + } + + ref.get().then((value) { + setState(() { + name = FirebaseAuth.instance.currentUser!.displayName ?? + value[ + "name"]; // fallback když uživatel je vytvořen skrz firebase admin + }); + }); + + setState(() { + _loading = false; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: Center( + child: SizedBox( + width: 90.w, + height: 100.h, + child: (_loading) + ? const LoadingWidget() + : Column( + children: [ + DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 35.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (name != "error") Text("Ahoj $name"), + TextButton( + onPressed: () => showAboutDialog( + context: context, + applicationName: "Kodelog", + applicationVersion: "2.0.1", + applicationLegalese: + "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", + children: [ + TextButton( + child: const Text("Zdrojový kód"), + onPressed: () => launchUrlString( + "https://github.com/Royal-Buccaneers/kodelog"), + ) + ]), + child: const Text( + "Licence", + style: Vzhled.textBtn, + ), + ), + TextButton( + onPressed: () async { + await FirebaseAuth.instance.signOut(); + if (!mounted) return; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (c) => const SignInPage()), + (route) => false); + }, + child: const Text( + "Odhlásit se", + style: Vzhled.textBtn, + ), + ) + ], + ), + ), + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 40.w, + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () => Navigator.of(context) + .pushReplacement(MaterialPageRoute( + builder: (context) => + const HlavniOkno())), + child: const Text( + "Denní přehled", + style: TextStyle(color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + const AllRecordsPage())); + }, + child: const Text( + "Všechny\nzáznamy", + style: TextStyle(color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + TextButton( + onPressed: () {}, + child: const Text( + "Nastavení", + style: TextStyle( + color: Vzhled.textColor, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 5), + OutlinedButton( + onPressed: () => + showCreateItemDialog(context), + style: Vzhled.orangeCudlik, + child: const Text( + "Přidat záznam", + ), + ) + ], + ), + ), + ], + ), + const SizedBox(height: 5), + Expanded( + child: MyContainer( + width: 90.w, + child: SingleChildScrollView( + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + const Text("Uživatelé", + style: Vzhled.mensiAleVelkyText), + OutlinedButton( + onPressed: () => showNewUser(), + style: Vzhled.orangeCudlik, + child: const Text( + "Přidat uživatele", + ), + ), + ], + ), + const SizedBox(height: 40), + const Padding( + padding: EdgeInsets.only(right: 8.0, left: 8), + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + mainAxisAlignmentMobile: + MainAxisAlignment.center, + children: [ + SizedBox( + width: 100, + child: Text( + "Jméno", + ), + ), + SizedBox( + width: 150, + child: Text( + "Email", + ), + ), + SizedBox( + width: 150, + child: Text("Username"), + ), + SizedBox( + width: 50, + child: Text("Admin"), + ), + Text("Nastavení") + ], + ), + ), + const SizedBox(height: 10), + SizedBox( + width: 80.w, + child: const Divider(color: Vzhled.purple), + ), + const SizedBox(height: 20), + SingleChildScrollView( + child: StreamBuilder( + stream: FirebaseFirestore.instance + .collection("users") + .snapshots(), + builder: (context, snapshot) { + if (snapshot.hasError) { + return const Text( + "Nastal error :C..."); + } else if (snapshot.hasData) { + var docs = snapshot.data!.docs; + return Column( + children: List.generate( + docs.length, + (index) { + var data = docs[index].data(); + + return Padding( + padding: + const EdgeInsets.all(8.0), + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment + .spaceBetween, + mainAxisAlignmentMobile: + MainAxisAlignment + .center, + children: [ + SizedBox( + width: 100, + child: Text( + data["name"], + style: const TextStyle( + fontWeight: + FontWeight + .bold), + ), + ), + SizedBox( + width: 150, + child: Text( + data["email"], + ), + ), + SizedBox( + width: 150, + child: Text( + data["username"] ?? + "Prozatím nic"), + ), + SizedBox( + width: 50, + child: Icon( + data["isAdmin"] ?? + false + ? Icons.done + : Icons + .do_disturb), + ), + (data["username"] != + "admin") + ? PopupMenuButton( + onSelected: + (value) { + switch (value) { + case 'Změnit práva': + showPrava( + docs[index] + .id, + data[ + "isAdmin"]); + break; + case "Upravit username": + showUpravitUsername( + docs[index] + .id, + data[ + "username"]); + break; + case 'Odstranit': + showOdstranit( + docs[index] + .id); + break; + } + }, + itemBuilder: + (BuildContext + context) { + return { + 'Změnit práva', + "Upravit username", + 'Odstranit' + }.map((String + choice) { + return PopupMenuItem< + String>( + value: + choice, + child: Text( + choice), + ); + }).toList(); + }, + ) + : const SizedBox( + width: 40) + ], + ), + ); + }, + ), + ); + } + return const LoadingWidget(); + }), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } + + showNewUser() async { + GlobalKey key = GlobalKey(); + String jmeno = ""; + String email = ""; + String username = ""; + bool isAdmin = false; + + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("Nový uživatel", style: Vzhled.velkyText), + scrollable: true, + content: StatefulBuilder( + builder: (context, setState) { + return SizedBox( + width: 50.w, + child: Form( + key: key, + child: Column( + children: [ + TextFormField( + decoration: Vzhled.inputDecoration("Jméno"), + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + onChanged: (value) { + jmeno = value; + }, + ), + const SizedBox(height: 10), + TextFormField( + decoration: Vzhled.inputDecoration("Username"), + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + onChanged: (value) { + username = value; + }, + ), + const SizedBox(height: 10), + TextFormField( + decoration: Vzhled.inputDecoration("Email"), + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } else if (!RegExp(r'[\w\.]+@[a-z0-9]+\.[a-z]{1,3}') + .hasMatch(value)) { + return "Neplatný e-mail!"; + } + return null; + }, + onChanged: (value) { + email = value; + }, + ), + const SizedBox(height: 10), + DropdownButton( + value: isAdmin, + items: ["Admin", "Uživatel"] + .map((e) => DropdownMenuItem( + value: e == "Admin" ? true : false, + child: Text(e))) + .toList(), + onChanged: (value) { + setState(() { + isAdmin = value!; + }); + }, + ), + const SizedBox(height: 20), + OutlinedButton( + style: Vzhled.orangeCudlik, + onPressed: () async { + if (key.currentState!.validate()) { + // kontrola ci niekto nevyuziva username + var usernameRef = await FirebaseFirestore.instance + .collection("users") + .where("username", isEqualTo: username) + .get(); + + if (usernameRef.docs.isNotEmpty) { + // ignore: use_build_context_synchronously + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text( + "Toto username patří jinému uživateli, prosím napište nové username."), + ), + ); + + return; + } + + // kontrola ci niekto nevyuziva email + var emailRef = await FirebaseFirestore.instance + .collection("users") + .where("email", isEqualTo: email) + .get(); + + if (emailRef.docs.isNotEmpty) { + // ignore: use_build_context_synchronously + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text( + "Tento email patří jinému uživateli, prosím jiný email."), + ), + ); + + return; + } + + FirebaseFirestore.instance.collection("users").add({ + "email": email, + "name": jmeno, + "isAdmin": isAdmin, + "favourite": "Dart", + "username": username + }).then((value) { + Navigator.of(context, rootNavigator: true) + .pop("dialog"); + + showDialog( + context: context, + builder: (_) => const AlertDialog( + scrollable: true, + content: Text("Uživatel vytvořen")), + ); + }); + } + }, + child: const Text("Přidat"), + ), + const SizedBox(height: 20), + const Text( + "Uživatel si při prvním přihlášení vytvoří heslo sám", + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + }, + ), + ), + ); + } + + showUpravitUsername(id, oldUsername) async { + GlobalKey key = GlobalKey(); + String newUsername = oldUsername; + + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("Username", style: Vzhled.velkyText), + scrollable: true, + content: SizedBox( + width: 50.w, + child: Form( + key: key, + child: Column( + children: [ + TextFormField( + initialValue: oldUsername, + decoration: Vzhled.inputDecoration("Nové username"), + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + onChanged: (value) { + newUsername = value; + }, + ), + const SizedBox(height: 15), + OutlinedButton( + style: Vzhled.orangeCudlik, + onPressed: () async { + if (key.currentState!.validate()) { + // kontrola ci niekto nevyuziva username + var usernameRef = await FirebaseFirestore.instance + .collection("users") + .where("username", isEqualTo: newUsername) + .get(); + + if (newUsername.isEmpty) { + // ignore: use_build_context_synchronously + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text("Toto pole je povinné!"), + ), + ); + + return; + } + + if (usernameRef.docs.isNotEmpty && + oldUsername != newUsername) { + // ignore: use_build_context_synchronously + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text( + "Toto username patří jinému uživateli, prosím napište nové username."), + ), + ); + + return; + } + + FirebaseFirestore.instance + .collection("users") + .doc(id) + .update({"username": newUsername}).then((value) => + Navigator.of(context, rootNavigator: true) + .pop("dialog")); + } + }, + child: const Text("Uložit")) + ], + ), + ), + ), + ), + ); + } + + showPrava(id, isAdmin) async { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("Práva uživatele", style: Vzhled.velkyText), + scrollable: true, + content: SizedBox( + width: 20.w, + child: DropdownButton( + value: isAdmin, + items: ["Admin", "Uživatel"] + .map((e) => DropdownMenuItem( + value: e == "Admin" ? true : false, child: Text(e))) + .toList(), + onChanged: (value) { + FirebaseFirestore.instance + .collection("users") + .doc(id) + .update({"isAdmin": value}); + + Navigator.of(context, rootNavigator: true).pop("dialog"); + }, + )), + ), + ); + } + + showOdstranit(id) async { + showReallyDelete(context, () { + FirebaseFirestore.instance.collection("users").doc(id).delete(); + Navigator.of(context, rootNavigator: true).pop("dialog"); + }, doNavigatorPop: false); + } +} diff --git a/lib/utils/csv.dart b/lib/utils/csv.dart new file mode 100644 index 0000000..b905a4d --- /dev/null +++ b/lib/utils/csv.dart @@ -0,0 +1,152 @@ +import 'dart:convert'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:csv/csv.dart'; +import 'package:denikprogramatora/utils/datum_cas.dart'; +import 'package:denikprogramatora/utils/loading_widget.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +final csvHeader = "id,date,time_spent,language,rating,description".split(","); + +Future importCsv(String name) async { + FilePickerResult? result = await FilePicker.platform.pickFiles( + dialogTitle: "Vyberte CSV soubor s databází", + type: FileType.custom, + allowedExtensions: ['csv']); + if (result == null) return -1; + var content = const CsvToListConverter(eol: "\n").convert( + String.fromCharCodes( + result.files.first.bytes!)); // načíst a rozdělit na řádky + + // Zkontrolovat počet sloupců + if (content.first.length != csvHeader.length) { + throw "CSV soubor není v platném formátu: neplatný počet sloupců"; + } + + Map> csv = {}; + var nazvySloupcu = []; + for (var radek in content) { + if (nazvySloupcu.isEmpty) { + // získat názvy sloupců + nazvySloupcu = radek; + if (nazvySloupcu.any((element) => !csvHeader.contains(element))) { + throw "CSV soubor není v platném formátu: neznámý název sloupce"; + } + for (var nazevSloupce in nazvySloupcu) { + csv[nazevSloupce] = []; + } + continue; + } + if (radek.length != nazvySloupcu.length) continue; + + // zkontrolovat, že máme všechny potřebné údaje v řádku + var j = 0; + var neplatny = false; + for (var sloupec in radek) { + if ((nazvySloupcu[j] != "description") && sloupec == "") { + neplatny = true; + break; + } + j++; + } + if (neplatny) continue; + + // přiřadit hodnotu sloupce do mapy + var i = 0; + for (var sloupec in radek) { + csv[nazvySloupcu[i]]!.add(sloupec); + i++; + } + } + + var userDoc = await FirebaseFirestore.instance + .collection("users/${FirebaseAuth.instance.currentUser!.uid}/records") + .get(); + var p = 0; + var pouzitaId = []; + for (var i = 0; i < csv[nazvySloupcu[0]]!.length; i++) { + var id = csv[nazvySloupcu[nazvySloupcu.indexOf("id")]]![i]; + if (pouzitaId.contains(id)) continue; + pouzitaId.add(id); + if (userDoc.docs.any((element) => element.id == id)) { + continue; + } // přeskočit dokumenty se stejným ID + + var date = csv[nazvySloupcu[nazvySloupcu.indexOf("date")]]![i].split("-"); + + var timeToSec = textToSec( + csv[nazvySloupcu[nazvySloupcu.indexOf("time_spent")]]![i] as String); + if (timeToSec == null) continue; + var timeSpent = Duration(seconds: timeToSec); + + var lang = csv[nazvySloupcu[nazvySloupcu.indexOf("language")]]![i]; + Map jazyk = {}; + if (jazyky.any((element) => element["jazyk"] == lang)) { + jazyk = jazyky.where((element) => element["jazyk"] == lang).toList()[0]; + } else { + jazyk = {"jazyk": lang, "barva": 0xffffffff}; + } + + var rating = csv[nazvySloupcu[nazvySloupcu.indexOf("rating")]]![i]; + if (rating > 5 || rating < 0) rating = 0; + + var desc = csv[nazvySloupcu[nazvySloupcu.indexOf("description")]]![i]; + DateTime? d; + try { + d = DateTime.parse( + "${date[2]}-${int.parse(date[1]) < 10 ? '0${date[1]}' : date[1]}-${int.parse(date[0]) < 10 ? "0${date[0]}" : date[0]}"); + } catch (e) { + continue; + } + + await FirebaseFirestore.instance + .collection("users/${FirebaseAuth.instance.currentUser!.uid}/records") + .doc(id) + .set({ + "date": d, + "time_spent": timeSpent.durationText, + "time_spentRaw": timeSpent.inSeconds, + "programming_language": jazyk, + "rating": rating, + "descriptionRaw": desc, + "description": null, + "programmer": FirebaseAuth.instance.currentUser!.uid, + "categories": [] + }); + p++; + } + return p; +} + +Future> exportCsv() async { + List> csv = [csvHeader]; + var records = await FirebaseFirestore.instance + .collection("users/${FirebaseAuth.instance.currentUser!.uid}/records") + .get(); + for (var rec in records.docs) { + var data = rec.data(); + csv.add([ + rec.id, + (data['date'] as Timestamp).toDate().rawDateString, + Duration(seconds: data['time_spentRaw']).durationText, + data['programming_language']['jazyk'], + data['rating'], + data['descriptionRaw'] + ]); + } + return utf8.encode(const ListToCsvConverter(eol: "\n").convert(csv)); +} + +/// Převede `40 hodin 50 minut` na sekundy +int? textToSec(String vstup) { + var match = + RegExp(r'(\d+) hodin(?: |y |a )(\d+) minut(?:$|a$|y$)').firstMatch(vstup); + if (match == null) return null; + print(match.group(1)); + print(match.group(2)); + var h = int.tryParse(match.group(1)!); + var m = int.tryParse(match.group(2)!); + if (h == null || m == null) return null; + return h * 3600 + m * 60; +} diff --git a/lib/utils/datum.dart b/lib/utils/datum.dart deleted file mode 100644 index 582635e..0000000 --- a/lib/utils/datum.dart +++ /dev/null @@ -1,5 +0,0 @@ -extension DateString on DateTime { - String get dateString => "$day. $month. $year"; - String get dateTimeString => - "$day. $month. $year $hour:${minute < 10 ? "0$minute" : minute}"; -} diff --git a/lib/utils/datum_cas.dart b/lib/utils/datum_cas.dart new file mode 100644 index 0000000..5347277 --- /dev/null +++ b/lib/utils/datum_cas.dart @@ -0,0 +1,11 @@ +extension DateString on DateTime { + String get dateString => "$day. $month. $year"; + String get dateTimeString => + "$day. $month. $year $hour:${minute < 10 ? "0$minute" : minute}"; + String get rawDateString => "$day-$month-$year"; +} + +extension TextDuration on Duration { + String get durationText => + "$inHours hodin${(inHours < 5 && inHours > 1) ? "y" : (inHours == 1) ? "a" : ""} ${inMinutes - inHours * 60} minut${(inMinutes - inHours * 60) < 5 && (inMinutes - inHours * 60) > 1 ? "y" : (inMinutes - inHours * 60) == 1 ? "a" : ""}"; +} diff --git a/lib/utils/loading_widget.dart b/lib/utils/loading_widget.dart index 865d7f9..58cf5b9 100644 --- a/lib/utils/loading_widget.dart +++ b/lib/utils/loading_widget.dart @@ -18,7 +18,7 @@ final jazyky = >[ {"jazyk": "C#", "barva": 0xff8200f3}, {"jazyk": "JavaScript", "barva": 0xfffdd700}, {"jazyk": "Python", "barva": 0xff0080ee}, - {"jazyk": "PHP🤢", "barva": 0xff00abff}, + {"jazyk": "PHP", "barva": 0xff00abff}, {"jazyk": "C++", "barva": 0xff1626ff}, {"jazyk": "Kotlin", "barva": 0xffe34b7c}, {"jazyk": "Java", "barva": 0xfff58219}, diff --git a/lib/utils/new_record_dialog.dart b/lib/utils/new_record_dialog.dart index 01a5dff..779efee 100644 --- a/lib/utils/new_record_dialog.dart +++ b/lib/utils/new_record_dialog.dart @@ -1,12 +1,12 @@ import 'package:denikprogramatora/okna/app.dart'; -import 'package:denikprogramatora/utils/datum.dart'; +import 'package:denikprogramatora/utils/datum_cas.dart'; import 'package:denikprogramatora/utils/devicecontainer.dart'; import 'package:denikprogramatora/utils/dokument.dart'; import 'package:denikprogramatora/utils/loading_widget.dart'; import 'package:denikprogramatora/utils/programmer.dart'; import 'package:denikprogramatora/utils/really_delete.dart'; import 'package:denikprogramatora/utils/vzhled.dart'; -import 'package:firebase_auth/firebase_auth.dart'; +import 'package:duration_picker/duration_picker.dart'; import 'package:fleather/fleather.dart'; import 'package:flutter/material.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; @@ -15,37 +15,33 @@ import 'package:uuid/uuid.dart'; import '../utils/my_category.dart'; Future showCreateItemDialog(context, - {DateTime? from, - DateTime? to, + {DateTime? originDate, + Duration? timeSpent, String? j, int hvezdicky = 0, String? p, List? k, String? originalId, ParchmentDocument? doc}) async { - DateTime fromDate = from ?? + DateTime date = originDate ?? DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, DateTime.now().hour - 1); - DateTime toDate = to ?? - DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, - DateTime.now().hour); // nastavení jazyka na oblíbený jazyk String jazyk = "C#"; if (j == null) { await ref.get().then((value) { - jazyk = value["favourite"]; + jazyk = value["favourite"] ?? "Dart"; }); } else { jazyk = j; } + timeSpent = timeSpent ?? const Duration(hours: 0, minutes: 0); + int review = hvezdicky; - Programmer programmer = p == null - ? Programmer(name, userUid) - : await Programmer.ziskatProgramatora( - FirebaseAuth.instance.currentUser!, p); + Programmer programmer = Programmer(name, "pK8iCAtMFiUUhK9FJd6HpWdwA3I3"); List categories = k ?? []; @@ -83,39 +79,25 @@ Future showCreateItemDialog(context, const SizedBox(height: 15), Row( children: [ - Text("Od ${toDate.dateTimeString}"), + Text( + "Datum ${date.day}. ${date.month}. ${date.year}"), const SizedBox(width: 15), TextButton( onPressed: () { showDatePicker( context: context, - initialDate: fromDate, + initialDate: date, firstDate: DateTime(DateTime.now().year - 5), lastDate: DateTime(DateTime.now().year + 5)) .then((value) { - showTimePicker( - context: context, - initialTime: - TimeOfDay.fromDateTime(fromDate)) - .then((time) { - if (value!.day == toDate.day && - value.month == toDate.month && - value.year == toDate.year && - (time!.hour > toDate.hour || - (time.hour <= toDate.hour && - time.minute > toDate.minute))) { - return; - } - setState(() { - fromDate = DateTime( - value.year, - value.month, - value.day, - time!.hour, - time.minute); - }); + setState(() { + date = DateTime( + value!.year, + value.month, + value.day, + ); }); }); }, @@ -126,89 +108,45 @@ Future showCreateItemDialog(context, ) ], ), - const SizedBox(height: 5), + const SizedBox(height: 30), Row( children: [ - Text("Do ${toDate.dateTimeString}"), + Text( + "Strávený čas ${timeSpent!.inHours} hodin ${timeSpent!.inMinutes - timeSpent!.inHours * 60} minut"), const SizedBox(width: 15), TextButton( - onPressed: () { - showDatePicker( + onPressed: () async { + await showDurationPicker( context: context, - initialDate: toDate, - firstDate: - DateTime(DateTime.now().year - 5), - lastDate: - DateTime(DateTime.now().year + 5)) - .then((value) { - showTimePicker( + baseUnit: BaseUnit.hour, + initialTime: + timeSpent ?? const Duration()) + .then((hours) { + showDurationPicker( context: context, - initialTime: - TimeOfDay.fromDateTime(toDate)) - .then((time) { - if (value!.day == fromDate.day && - value.month == fromDate.month && - value.year == fromDate.year && - (time!.hour < fromDate.hour || - (time.hour >= fromDate.hour && - time.minute < - fromDate.minute))) { - return; - } - setState(() { - toDate = DateTime(value.year, value.month, - value.day, time!.hour, time.minute); - }); - }); + baseUnit: BaseUnit.minute, + initialTime: Duration( + minutes: (timeSpent ?? + const Duration()) + .inMinutes - + (timeSpent ?? + const Duration()) + .inHours * + 60)) + .then((minutes) => setState(() { + timeSpent = Duration( + hours: hours!.inHours, + minutes: minutes!.inMinutes); + })); }); }, child: const Text( - ("Změnit"), + "Změnit", style: Vzhled.textBtn, ), ) ], ), - const SizedBox(height: 30), - const Align( - alignment: Alignment.centerLeft, - child: Text( - "Strávený čas", - style: Vzhled.nadpis, - ), - ), - const SizedBox(height: 15), - Align( - alignment: Alignment.centerLeft, - child: Text( - "${toDate.difference(fromDate).inHours} ${toDate.difference(fromDate).inHours == 1 ? "hodina" : toDate.difference(fromDate).inHours > 1 && toDate.difference(fromDate).inHours < 5 ? "hodiny" : "hodin"}${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60 == 0) ? "" : " a ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60)} ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) == 1 ? "minuta" : (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) > 1 && (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) < 5 ? "minuty" : "minut"}"}"), - ), - const SizedBox(height: 30), - const Align( - alignment: Alignment.centerLeft, - child: Text( - "Programátor", - style: Vzhled.nadpis, - ), - ), - const SizedBox(height: 15), - Align( - alignment: Alignment.centerLeft, - child: TextButton( - onPressed: () async { - await showProgrammersDialog(context, doc: doc) - .then((value) { - setState(() { - programmer = value; - }); - }); - }, - child: Text( - programmer.name, - style: Vzhled.textBtn, - ), - ), - ), ], ), ), @@ -350,35 +288,33 @@ Future showCreateItemDialog(context, onPressed: () { if (originalId == null) { ref.collection("records").add({ - "fromDate": fromDate, - "toDate": toDate, - "codingTime": - "${toDate.difference(fromDate).inHours} ${toDate.difference(fromDate).inHours == 1 ? "hodina" : toDate.difference(fromDate).inHours > 1 && toDate.difference(fromDate).inHours < 5 ? "hodiny" : "hodin"}${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60 == 0) ? "" : " a ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60)} ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) == 1 ? "minuta" : (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) > 1 && (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) < 5 ? "minuty" : "minut"}"}", + "date": DateTime(date.year, date.month, date.day), + "time_spent": timeSpent!.durationText, + "time_spentRaw": timeSpent!.inSeconds, "programmer": programmer.id, - "programmerName": programmer.name, - "language": jazyky + "programming_language": jazyky .where((element) => element["jazyk"] == jazyk) .toList()[0], - "review": review, + "rating": review, "categories": categories.map((e) => e.id).toList(), "description": controller.document.toActualJson(), + "descriptionRaw": controller.document.toPlainText() }).then((value) => Navigator.of(context, rootNavigator: true) .pop("dialog")); return; } ref.collection("records").doc(originalId).update({ - "fromDate": fromDate, - "toDate": toDate, - "codingTime": - "${toDate.difference(fromDate).inHours} ${toDate.difference(fromDate).inHours == 1 ? "hodina" : toDate.difference(fromDate).inHours > 1 && toDate.difference(fromDate).inHours < 5 ? "hodiny" : "hodin"}${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60 == 0) ? "" : " a ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60)} ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) == 1 ? "minuta" : (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) > 1 && (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) < 5 ? "minuty" : "minut"}"}", + "date": DateTime(date.year, date.month, date.day), + "time_spentRaw": timeSpent!.inSeconds, + "time_spent": timeSpent!.durationText, "programmer": programmer.id, - "programmerName": programmer.name, - "language": jazyky + "programming_language": jazyky .where((element) => element["jazyk"] == jazyk) .toList()[0], - "review": review, + "rating": review, "categories": categories.map((e) => e.id).toList(), + "descriptionRaw": controller.document.toPlainText(), "description": controller.document.toActualJson(), }).then((value) => Navigator.of(context, rootNavigator: true).pop("dialog")); @@ -398,277 +334,6 @@ Future showCreateItemDialog(context, ); } -Future showProgrammersDialog(context, - {ParchmentDocument? doc, bool jenMenit = false}) async { - bool showAddProgrammer = false; - bool editing = false; - GlobalKey key = GlobalKey(); - TextEditingController nameCon = TextEditingController(); - late String editId; - - Programmer programmer = Programmer(name, userUid); - - await showDialog( - context: context, - builder: (_) => AlertDialog( - title: const Text("Programátoři", style: Vzhled.velkyText), - scrollable: true, - content: SizedBox( - width: 50.w, - child: StatefulBuilder( - builder: (context, setState) { - return showAddProgrammer - ? Form( - key: key, - child: Column( - children: [ - Row( - children: [ - Expanded( - child: TextFormField( - controller: nameCon, - decoration: Vzhled.inputDecoration("Jméno"), - validator: (value) { - if (value!.trim().isEmpty) { - return "Toto pole je povinné!"; - } - return null; - }, - ), - ), - const SizedBox(width: 5), - TextButton( - onPressed: () { - if (key.currentState!.validate()) { - if (editing) { - ref - .collection("programmers") - .doc(editId) - .update({"name": nameCon.text}); - } else { - var uuid = const Uuid(); - String id = uuid.v1(); - - ref - .collection("programmers") - .doc(id) - .set({"name": nameCon.text, "id": id}); - } - nameCon.text = ""; - - setState(() { - editing = false; - showAddProgrammer = false; - }); - } - }, - child: Text( - editing ? "Uložit" : "Přidat", - style: Vzhled.textBtn, - ), - ), - ], - ), - const SizedBox(height: 5), - TextButton( - onPressed: () { - setState(() { - showAddProgrammer = false; - }); - }, - child: const Text( - "Zpátky", - style: Vzhled.textBtn, - ), - ), - ], - ), - ) - : Column( - children: [ - Material( - color: Vzhled.dialogColor, - child: InkWell( - splashColor: (jenMenit) ? Colors.transparent : null, - onTap: () { - if (jenMenit) return; - programmer = Programmer(name, userUid); - Navigator.of(context, rootNavigator: true) - .pop("dialog"); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(name), - if (!jenMenit) - TextButton( - onPressed: () { - programmer = Programmer(name, userUid); - Navigator.of(context, rootNavigator: true) - .pop("dialog"); - }, - child: const Text( - "Vybrat", - style: Vzhled.textBtn, - ), - ), - ], - ), - ), - ), - ), - StreamBuilder( - stream: ref.collection("programmers").snapshots(), - builder: (context, snapshot) { - if (snapshot.hasData) { - var docs = snapshot.data!.docs; - - return Column( - children: List.generate(docs.length, (index) { - var data = docs[index].data(); - return Material( - color: Vzhled.dialogColor, - child: InkWell( - splashColor: (jenMenit) - ? Colors.transparent - : null, - onTap: () { - if (jenMenit) return; - programmer = Programmer( - data["name"], data["id"]); - Navigator.of(context, - rootNavigator: true) - .pop("dialog"); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text(data["name"]), - Row( - children: [ - if (!jenMenit) - TextButton( - onPressed: () { - programmer = Programmer( - data["name"], - data["id"]); - Navigator.of(context, - rootNavigator: - true) - .pop("dialog"); - }, - child: const Text( - "Vybrat", - style: Vzhled.textBtn, - ), - ), - IconButton( - onPressed: () { - editId = data["id"]; - - setState(() { - showAddProgrammer = true; - editing = true; - nameCon = - TextEditingController( - text: - data["name"]); - }); - }, - icon: const Icon(Icons.edit, - color: Vzhled.textColor), - ), - IconButton( - onPressed: () { - showReallyDelete(context, - () async { - Navigator.of(context, - rootNavigator: - true) - .pop("dialog"); - Navigator.of(context, - rootNavigator: - true) - .pop("dialog"); - if (doc != null) { - Navigator.of(context, - rootNavigator: - true) - .pop("dialog"); - } - - // deleting all records - await ref - .collection("records") - .where("programmer", - isEqualTo: - data["id"]) - .get() - .then((value) { - for (var snap - in value.docs) { - ref - .collection( - "records") - .doc(snap.id) - .delete(); - } - }); - - // deleting - await ref - .collection( - "programmers") - .doc(data["id"]) - .delete(); - }, - doNavigatorPop: false, - text: - "Odstranit programátora a všechny jeho záznamy?"); - }, - icon: const Icon(Icons.delete, - color: Vzhled.textColor), - ), - ], - ) - ], - ), - ), - ), - ); - }), - ); - } - return const LoadingWidget(); - }), - const SizedBox(height: 5), - TextButton( - onPressed: () { - setState(() { - showAddProgrammer = true; - }); - }, - child: const Text( - "Přidat nového programátora", - style: Vzhled.textBtn, - ), - ), - ], - ); - }, - ), - ), - ), - ); - - return programmer; -} - Future> showCategoriesDialog( context, List categories, {bool jenMenit = false}) async { diff --git a/lib/utils/razeni.dart b/lib/utils/razeni.dart index 3be7eae..418ba32 100644 --- a/lib/utils/razeni.dart +++ b/lib/utils/razeni.dart @@ -4,29 +4,29 @@ var razeni = [ // VZESTUPNĚ ČAS (QueryDocumentSnapshot> a, QueryDocumentSnapshot> b) => - ((a.data()["fromDate"] as Timestamp).toDate().hour == - (b.data()["fromDate"] as Timestamp).toDate().hour) - ? (a.data()["fromDate"] as Timestamp) + ((a.data()["date"] as Timestamp).toDate().hour == + (b.data()["date"] as Timestamp).toDate().hour) + ? (a.data()["date"] as Timestamp) .toDate() .minute - .compareTo((b.data()["fromDate"] as Timestamp).toDate().minute) - : (a.data()["fromDate"] as Timestamp) + .compareTo((b.data()["date"] as Timestamp).toDate().minute) + : (a.data()["date"] as Timestamp) .toDate() .hour - .compareTo((b.data()["fromDate"] as Timestamp).toDate().hour), + .compareTo((b.data()["date"] as Timestamp).toDate().hour), // SESTUPNĚ ČAS (QueryDocumentSnapshot> a, QueryDocumentSnapshot> b) => - ((b.data()["fromDate"] as Timestamp).toDate().hour == - (a.data()["fromDate"] as Timestamp).toDate().hour) - ? (b.data()["fromDate"] as Timestamp) + ((b.data()["date"] as Timestamp).toDate().hour == + (a.data()["date"] as Timestamp).toDate().hour) + ? (b.data()["date"] as Timestamp) .toDate() .minute - .compareTo((a.data()["fromDate"] as Timestamp).toDate().minute) - : (b.data()["fromDate"] as Timestamp) + .compareTo((a.data()["date"] as Timestamp).toDate().minute) + : (b.data()["date"] as Timestamp) .toDate() .hour - .compareTo((a.data()["fromDate"] as Timestamp).toDate().hour), + .compareTo((a.data()["date"] as Timestamp).toDate().hour), // VZESTUPNĚ HODNOCENÍ (QueryDocumentSnapshot> a, QueryDocumentSnapshot> b) => diff --git a/lib/utils/show_info_dialog.dart b/lib/utils/show_info_dialog.dart index d7b3290..efbe073 100644 --- a/lib/utils/show_info_dialog.dart +++ b/lib/utils/show_info_dialog.dart @@ -1,6 +1,6 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:denikprogramatora/okna/app.dart'; -import 'package:denikprogramatora/utils/datum.dart'; +import 'package:denikprogramatora/utils/datum_cas.dart'; import 'package:denikprogramatora/utils/devicecontainer.dart'; import 'package:denikprogramatora/utils/my_category.dart'; import 'package:denikprogramatora/utils/new_record_dialog.dart'; @@ -12,12 +12,11 @@ import 'package:flutter/material.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; void showInfoDialog( - context, Map data, String originalId) async { - var denOd = (data["fromDate"] as Timestamp).toDate().toLocal(); - var denDo = (data["toDate"] as Timestamp).toDate().toLocal(); + context, Map data, String originalId, String jmeno) async { + var date = (data["date"] as Timestamp).toDate().toLocal(); List categories = []; - if ((data["categories"] as List).isNotEmpty) { + if (((data["categories"] ?? []) as List).isNotEmpty) { for (var category in data["categories"]) { await ref.collection("categories").doc(category).get().then((value) { var data = value.data(); @@ -30,7 +29,9 @@ void showInfoDialog( showDialog( context: context, builder: (_) { - var document = ParchmentDocument.fromJson(data["description"]); + var document = (data["description"] != null && data["description"] != "") + ? ParchmentDocument.fromJson(data["description"]) + : (ParchmentDocument()..insert(0, data["descriptionRaw"])); var controller = FleatherController(document); return AlertDialog( @@ -51,13 +52,13 @@ void showInfoDialog( style: Vzhled.purpleCudlik, onPressed: () { showCreateItemDialog(context, - from: denOd, - to: denDo, + originDate: date, + timeSpent: Duration(seconds: data["time_spentRaw"]), k: categories, p: data["programmer"], - hvezdicky: data["review"], + hvezdicky: data["rating"], originalId: originalId, - j: data["language"]["jazyk"], + j: data["programming_language"]["jazyk"], doc: document) .then((_) => Navigator.of(context).pop()); }, @@ -69,7 +70,7 @@ void showInfoDialog( ), ], title: Text( - "Záznam ze dne ${denOd.dateString}", + "Záznam ze dne ${date.dateString}", style: Vzhled.dialogNadpis, ), scrollable: true, @@ -97,9 +98,7 @@ void showInfoDialog( style: Vzhled.nadpis, ), Text( - (Device.screenType == ScreenType.mobile) - ? "od ${denOd.dateTimeString}\ndo ${denDo.dateTimeString} (${data["codingTime"]})" - : "od ${denOd.dateTimeString} do ${denDo.dateTimeString} (${data["codingTime"]})", + "${date.dateString} (${data["time_spent"]})", ) ], ), @@ -113,9 +112,10 @@ void showInfoDialog( style: Vzhled.nadpis, ), Text( - data["language"]["jazyk"], + data["programming_language"]["jazyk"], style: TextStyle( - color: Color(data["language"]["barva"])), + color: Color( + data["programming_language"]["barva"])), ) ], ), @@ -138,7 +138,9 @@ void showInfoDialog( style: Vzhled.nadpis, ), Text( - data["programmerName"], + FirebaseAuth + .instance.currentUser!.displayName ?? + jmeno, ) ], ), @@ -156,7 +158,7 @@ void showInfoDialog( 5, (index) { return Icon(Icons.star, - color: (index + 1) <= data["review"] + color: (index + 1) <= data["rating"] ? Colors.yellow : Colors.grey); }, diff --git a/pubspec.lock b/pubspec.lock index 740401c..2b69643 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "6215ac7d00ed98300b72f45ed2b38c2ca841f9f4e6965fab33cbd591e45e4473" + sha256: "64fcb0dbca4386356386c085142fa6e79c00a3326ceaa778a2d25f5d9ba61441" url: "https://pub.dev" source: hosted - version: "1.0.13" + version: "1.0.16" async: dependency: transitive description: @@ -45,26 +45,26 @@ packages: dependency: "direct main" description: name: cloud_firestore - sha256: "9e775f9df26a165444bd5240f70bfee6f11b35c5e913e93ed4b06bf50b231325" + sha256: "65f148d9f5b4f389320abb45847120cf5e46094c1a8cbc64934ffc1e29688596" url: "https://pub.dev" source: hosted - version: "4.3.2" + version: "4.4.3" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - sha256: ab35c068896ff769ce7e8de8198228d512e7f056fc8f26b2ff53ea3f97c8545f + sha256: "43ccae09f7e0c82752e69c251c6dc5efcdff4ddcfc09564175a28657bbd74188" url: "https://pub.dev" source: hosted - version: "5.10.2" + version: "5.11.3" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - sha256: b7b52c2ad50d1105f2e0585a34288da415cf9d1037470985c7c57cce7b06d95f + sha256: e054c007217e28e07179bbae0564c2a4f6338a60bddb0c139e4834e953f4b95c url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.3.3" collection: dependency: transitive description: @@ -89,14 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.17.2" - cupertino_icons: + csv: dependency: "direct main" description: - name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + name: csv + sha256: "18aef53ab72181a0b5384562d18c8cbd57e941e24cb8e54eb41409d3d8abdc6d" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "5.0.1" diff_match_patch: dependency: transitive description: @@ -105,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.1" + duration_picker: + dependency: "direct main" + description: + name: duration_picker + sha256: "052b34dac04c29f3849bb3817a26c5aebe9e5f0697c3a374be87db2b84d75753" + url: "https://pub.dev" + source: hosted + version: "1.1.1" fake_async: dependency: transitive description: @@ -113,38 +121,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: d090ae03df98b0247b82e5928f44d1b959867049d18d73635e2e0bc3f49542b9 + url: "https://pub.dev" + source: hosted + version: "5.2.5" firebase_auth: dependency: "direct main" description: name: firebase_auth - sha256: "843e307e9b7faa026dd9970e584b5d53265fb5a0c4323883fecdce89ec05d56a" + sha256: "9907d80446466e638dad31c195150b305dffd145dc57610fcd12c72289432143" url: "https://pub.dev" source: hosted - version: "4.2.6" + version: "4.2.9" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "8702baa08ad5aa6daa023082d612ca168bf3f7de81e3d56e1df18321f76d675f" + sha256: c645fec50b0391aa878288f58fa4fe9762c271380c457aedf5c7c9b718604f68 url: "https://pub.dev" source: hosted - version: "6.11.8" + version: "6.11.11" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: "0c01b9c772ee730df03ac92102e538873558f908d6e42602f6ff9c61dead8d58" + sha256: "2dcf2a36852b9091741b4a4047a02e1f2c43a62c6cacec7df573a793a6543e6d" url: "https://pub.dev" source: hosted - version: "5.2.5" + version: "5.2.8" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: be13e431c0c950f0fc66bdb67b41b8059121d7e7d8bbbc21fb59164892d561f8 + sha256: fe30ac230f12f8836bb97e6e09197340d3c584526825b1746ea362a82e1e43f7 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" firebase_core_platform_interface: dependency: transitive description: @@ -157,10 +181,10 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "4b3a41410f3313bb95fd560aa5eb761b6ad65c185de772c72231e8b4aeed6d18" + sha256: "291fbcace608aca6c860652e1358ef89752be8cc3ef227f8bbcd1e62775b833a" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.1" fleather: dependency: "direct main" description: @@ -187,6 +211,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "4bef634684b2c7f3468c77c766c831229af829a0cd2d4ee6c1b99558bd14e5d2" + url: "https://pub.dev" + source: hosted + version: "2.0.8" flutter_test: dependency: "direct dev" description: flutter @@ -289,10 +321,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" quill_delta: dependency: transitive description: @@ -382,66 +414,66 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b + sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" url: "https://pub.dev" source: hosted - version: "6.1.9" + version: "6.1.10" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "3e2f6dfd2c7d9cd123296cab8ef66cfc2c1a13f5845f42c7a0f365690a8a7dd1" + sha256: "1f4d9ebe86f333c15d318f81dcdc08b01d45da44af74552608455ebdc08d9732" url: "https://pub.dev" source: hosted - version: "6.0.23" + version: "6.0.24" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815" + sha256: c9cd648d2f7ab56968e049d4e9116f96a85517f1dd806b96a86ea1018a3a82e5 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.1.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc" + sha256: e29039160ab3730e42f3d811dc2a6d5f2864b90a70fb765ea60144b03307f682 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "41988b55570df53b3dd2a7fc90c76756a963de6a8c5f8e113330cb35992e2094" + sha256: "2dddb3291a57b074dade66b5e07e64401dd2487caefd4e9e2f467138d8c7eb06" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6" + sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0" + sha256: "574cfbe2390666003c3a1d129bdc4574aaa6728f0c00a4829a81c316de69dd9b" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.0.15" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615 + sha256: "97c9067950a0d09cbd93e2e3f0383d1403989362b97102fbf446473a48079a4b" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" uuid: dependency: "direct main" description: @@ -458,6 +490,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + win32: + dependency: transitive + description: + name: win32 + sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=2.18.5 <3.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6210485..af92597 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,6 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 firebase_core: ^2.4.0 responsive_sizer: ^3.1.1 firebase_auth: ^4.2.1 @@ -46,6 +45,9 @@ dependencies: multi_select_flutter: ^4.1.3 fleather: ^1.4.0 url_launcher: ^6.1.8 + file_picker: ^5.2.5 + csv: ^5.0.1 + duration_picker: ^1.1.1 dev_dependencies: flutter_test: diff --git a/web/manifest.json b/web/manifest.json index e4b342c..339b4f1 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -1,6 +1,6 @@ { - "name": "denikprogramatora", - "short_name": "denikprogramatora", + "name": "Kodelog", + "short_name": "Kodelog", "start_url": ".", "display": "standalone", "background_color": "#0175C2",