diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..827d37b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +**/.venv/ +**/__pycache__/ diff --git a/.gitignore b/.gitignore index 870f370..7c81190 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,19 @@ +# Secrets +/.env + # Locally installed environments -/.venv/ +/app/.venv/ /node_modules/ -# Local development database +# Local development data /.postgres/ +/.gotify/ # Cache files and directories **/*.pyc **/__pycache__/ /.parcel-cache/ - -# Bundled files -/dist/ +/app/static/dist/ # Latex compiled files **/*.aux diff --git a/.postcssrc b/.postcssrc index f56b326..fd23504 100644 --- a/.postcssrc +++ b/.postcssrc @@ -1,7 +1,5 @@ { -  "plugins": { -    "postcss-import": {}, -    "tailwindcss/nesting": {}, -    "tailwindcss": {}, -  } + "plugins": { + "tailwindcss": {}, + } } diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..f25042e --- /dev/null +++ b/Caddyfile @@ -0,0 +1,10 @@ +:8000 { + handle * { + reverse_proxy * medwings-django:8000 + } + + log { + output stderr + format console + } +} diff --git a/README.md b/README.md index dee2640..ff3ba5b 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,89 @@ # MEDWings -Mobile Early Deterioration Warning System. +Medwings is the *Mobile Early Deterioration Warning System*. +It is a proof of concept for a remote patient monitoring system, designed for use by elevated-risk patients +outside of direct medical supervision, such as at home or on the go. -# MEWS +The application utilizes smart medical devices to access vitals data gathered by users remotely. +Data is aggregated, and a clinical early warning score is calculated based on the readings. -The following vital signs need to be recorded for a MEWS calculation: +The Medwings application is split into different modules, each handling a specific responsibility: -* Heart Rate -* SPO2 -* Blood Pressure -* Body Temperature -* Respiration Rate +- [core](./app/core/README.md): global files and configuration +- [authentication](./app/authentication/README.md): user creation and login/logout management +- [medwings](./app/medwings/README.md): everything related to vitals data and MEWS calculation +- [gotify](./app/gotify/README.md): interfaces to the notification server +- [withings](./app/withings/README.md): interfaces to the Withings API -A detailed explanation and formula [can be found here](https://www.mdcalc.com/calc/1875/modified-early-warning-score-mews-clinical-deterioration#evidence). +You can read more about each module and its functionality in each section mentioned above. -# Handheld Devices +## Development -We have procured the following devices for vitals data measurement: +### Sensitive Configuration Data -* [Withings Scanwatch](https://www.withings.com/de/en/scanwatch) - * Heart Rate, SPO2 -* [Withings Thermo](https://www.withings.com/de/en/thermo) - * Body Surface Temperature -* [WIthings BPM Core](https://www.withings.com/de/en/bpm-core) - * Blood Pressure +To avoid leaking sensitive configuration data, such as database passwords or API keys, all such values are stored in the +`.env`-file. -## API Access +Prior to running the application, you must create a file called `.env` in the project root. +The file contains the following environment variables: -Data is gathered by taking measurements using the devices, either actively (BP sleeve, thermometer) or passively (smartwatch). -The devices are connected to the Withings mobile app. -The mobile app then regularly pushes gathered data to the Withings cloud. +```conf +TIMEZONE=Europe/Berlin +PG_NAME=medwings +PG_USER=medwings +PG_PASSWORD=secret +PG_HOST=medwings-postgres +PG_PORT=5432 +GOTIFY_USER=gotify +GOTIFY_PASSWORD=secret +GOTIFY_HOST=medwings-gotify +GOTIFY_PUBLIC_URL=https://notifications.medwings.example.com/ +WITHINGS_CLIENT_ID=abc123myClientId +WITHINGS_CLIENT_SECRET=abc123myClientSecret +``` -The Withings Dev Free plan allows for 120 API requests per minute. -Access to vitals data is available through the [Withings API](https://developer.withings.com/). +You should set the values of the following variables: -A detailed [API integration guide](https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/public-health-data-api-overview/), -as well as an [API reference guide](https://developer.withings.com/api-reference) are available online. +| variable | description | value | +|----------|-------------|-------| +| PG_PASSWORD | password for the PostgreSQL admin user | a random string of 32 characters | +| GOTIFY_USER | name of the Gotify admin user | a random string of 32 characters | +| GOTIFY_PASSWORD | password for the Gotify admin user | a random string of 32 characters | +| GOTIFY_PUBLIC_URL | URL where your public Gotify server can be reached | this depends on your deployment environment | +| WITHINGS_CLIENT_ID | Your Withings API client id | see [Withings API](./app/withings/README.md#api-access) | +| WITHINGS_CLIENT_SECRET | Your Withings API client secret | see [Withings API](./app/withings/README.md#api-access) | -# Development -To start the development compose-stack, run the following command: +### Starting the dev environment + +Once your environment vars are set up, you can run the backend and webserver, by running the following command: ```bash sudo docker-compose -f development.docker-compose.yml up --force-recreate --build --remove-orphans ``` -Run [alembic](https://alembic.sqlalchemy.org/en/latest/) database migrations inside the running container like so: +In a separate terminal, you should also start the frontend asset bundler: ```bash -sudo docker exec -w /app/backend/database -it backend alembic upgrade head +npm run start ``` -To run commands inside the backend container, run the following: +It supports file watching and automatic recompilation of the project's CSS and JS bundle. + +#### Running commands inside the container + +To run commands inside the django container, run the following: ```bash -sudo docker exec -it backend +sudo docker exec -itu django medwings-django +``` + +Run database migrations inside the running container like so: + +```bash +sudo docker exec -itu medwings-django python manage.py migrate +``` + +To enter django's interactive shell, run: +```bash +sudo docker exec -itu medwings-django python manage.py shell ``` diff --git a/app/authentication/README.md b/app/authentication/README.md new file mode 100644 index 0000000..f2554a1 --- /dev/null +++ b/app/authentication/README.md @@ -0,0 +1,6 @@ +# Authentication + +This module handles user management, such as user login and user registration. + +This also includes handling the oauth2 token retrieval in the background, which is necessary to retrieve +data from the Withings Public Health Cloud on behalf of users. diff --git a/backend/__init__.py b/app/authentication/__init__.py similarity index 100% rename from backend/__init__.py rename to app/authentication/__init__.py diff --git a/app/authentication/apps.py b/app/authentication/apps.py new file mode 100644 index 0000000..8bab8df --- /dev/null +++ b/app/authentication/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthenticationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'authentication' diff --git a/app/authentication/forms.py b/app/authentication/forms.py new file mode 100644 index 0000000..09ef910 --- /dev/null +++ b/app/authentication/forms.py @@ -0,0 +1,12 @@ +from django import forms +from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.models import User + +class CustomUserCreationForm(UserCreationForm): + first_name = forms.CharField(required=True) + last_name = forms.CharField(required=True) + email = forms.EmailField(required=True) + + class Meta: + model = User + fields = ("username", "first_name", "last_name", "email", "password1", "password2") diff --git a/backend/crud/__init__.py b/app/authentication/migrations/__init__.py similarity index 100% rename from backend/crud/__init__.py rename to app/authentication/migrations/__init__.py diff --git a/app/authentication/templates/authentication/login.html b/app/authentication/templates/authentication/login.html new file mode 100644 index 0000000..ef982d0 --- /dev/null +++ b/app/authentication/templates/authentication/login.html @@ -0,0 +1,64 @@ +{% extends 'core/base.html' %} +{% load static %} +{% load widget_tweaks %} +{% block title %} + Medwings | Log In +{% endblock title %} + +{% block content %} +
+

Log In

+
+ {% csrf_token %} +
+ Please enter your login details + + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} + +
+ +
+ {% render_field form.username|add_error_class:"error" %} + + {% if form.username.errors %} +
+ {% for error in form.username.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ {% render_field form.password|add_error_class:"error" %} + + {% if form.password.errors %} +
+ {% for error in form.password.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ + + +
+
+ + {# Assumes you set up the password_reset view in your URLconf #} + {% comment %}

Lost password?

{% endcomment %} +
+{% endblock content %} diff --git a/app/authentication/templates/authentication/logout.html b/app/authentication/templates/authentication/logout.html new file mode 100644 index 0000000..8f2e9bf --- /dev/null +++ b/app/authentication/templates/authentication/logout.html @@ -0,0 +1,4 @@ +{% extends 'core/base.html' %} +{% block content %} +

You have been logged out.

+{% endblock content %} diff --git a/app/authentication/templates/authentication/register-continue.html b/app/authentication/templates/authentication/register-continue.html new file mode 100644 index 0000000..b8e8f19 --- /dev/null +++ b/app/authentication/templates/authentication/register-continue.html @@ -0,0 +1,145 @@ +{% extends 'core/base.html' %} +{% load static %} +{% load widget_tweaks %} +{% block title %} + Medwings | Sign Up +{% endblock title %} + +{% block content %} +
+

Register

+
+ {% csrf_token %} +
+ Please enter your profile information + + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} + +
+ +
+ {% render_field user_form.first_name|add_error_class:"error" %} + + {% if user_form.first_name.errors %} +
+ {% for error in user_form.first_name.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ {% render_field user_form.last_name|add_error_class:"error" %} + + {% if user_form.last_name.errors %} +
+ {% for error in user_form.last_name.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ {% render_field profile_form.date_of_birth|add_error_class:"error" %} + + {% if profile_form.date_of_birth.errors %} +
+ {% for error in profile_form.date_of_birth.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ {% render_field profile_form.sex|add_error_class:"error" %} + + {% if profile_form.sex.errors %} +
+ {% for error in profile_form.sex.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ {% render_field user_form.email|add_error_class:"error" %} + + {% if user_form.email.errors %} +
+ {% for error in user_form.email.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ {% render_field user_form.username|add_error_class:"error" %} + + {% if user_form.username.errors %} +
+ {% for error in user_form.username.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ {% render_field user_form.password1|add_error_class:"error" %} + + {% if user_form.password1.errors %} +
+ {% for error in user_form.password1.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ {% render_field user_form.password2|add_error_class:"error" %} + + {% if user_form.password2.errors %} +
+ {% for error in user_form.password2.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ + + +
+
+
+{% endblock content %} diff --git a/app/authentication/templates/authentication/register-finalize.html b/app/authentication/templates/authentication/register-finalize.html new file mode 100644 index 0000000..638de0c --- /dev/null +++ b/app/authentication/templates/authentication/register-finalize.html @@ -0,0 +1,19 @@ +{% extends 'core/base.html' %} +{% load static %} +{% block title %} + Medwings | Sign Up +{% endblock title %} + +{% block content %} +
+

Register

+

To finalize your registration and receive regular notifications, please take the following steps:

+
+
    +
  1. Install the Gotify App on your smartphone, available on F-Droid or on the Google Play Store.
  2. +
  3. Open the app, and connect to our notification server {{ gotify_public_url }} and log in using your Medwings username and password.
  4. +
+
+

All set! You can now log in to view your data or take your first MEWS measurement. +

+{% endblock content %} diff --git a/app/authentication/templates/authentication/register-init.html b/app/authentication/templates/authentication/register-init.html new file mode 100644 index 0000000..b245439 --- /dev/null +++ b/app/authentication/templates/authentication/register-init.html @@ -0,0 +1,21 @@ +{% extends 'core/base.html' %} +{% load static %} +{% block title %} + Medwings | Sign Up +{% endblock title %} + +{% block content %} +
+

Register

+

+ Something something glad you're signing up. +

+
+

To get started, please allow us to access your health data

+ Link Withings Account +
+

+ Something something why this is necessary. +

+
+{% endblock content %} diff --git a/app/authentication/urls.py b/app/authentication/urls.py new file mode 100644 index 0000000..96477d2 --- /dev/null +++ b/app/authentication/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from django.contrib.auth import views as auth_views + +from . import views + +urlpatterns = [ + path("login/", auth_views.LoginView.as_view(template_name="authentication/login.html"), name="login"), + path("logout/", auth_views.LogoutView.as_view(template_name="authentication/logout.html"), name="logout"), + path("register/init/", views.register_init, name="register-init"), + path("register/continue/", views.register_continue, name="register-continue"), + path("register/finalize/", views.register_finalize, name="register-finalize"), +] diff --git a/app/authentication/views.py b/app/authentication/views.py new file mode 100644 index 0000000..05128e5 --- /dev/null +++ b/app/authentication/views.py @@ -0,0 +1,136 @@ +from urllib.parse import urlencode +from uuid import uuid4 +import logging + +from django.shortcuts import redirect, render +from django.conf import settings +from django.urls import reverse +from django.core.exceptions import PermissionDenied +from django.http import HttpResponseBadRequest +from django.utils.dateparse import parse_datetime + +import withings.api +import withings.models +import gotify.api +import gotify.models +from medwings.forms import ProfileForm +from .forms import CustomUserCreationForm + + +def register_init(request): + if request.user.is_authenticated: + raise PermissionDenied('You are already registered and logged in.') + + # Generate a unique token and save it for later + request.session.flush() + registration_sequence_token = str(uuid4()) + request.session['registration_sequence_token'] = registration_sequence_token + + auth_url_base = 'https://account.withings.com/oauth2_user/authorize2' + auth_url_params = { + 'response_type': 'code', + 'client_id': settings.WITHINGS_CONFIG['CLIENT_ID'], + 'scope': 'user.metrics,user.activity', + 'redirect_uri': request.build_absolute_uri(reverse('register-continue')), + 'state': registration_sequence_token + } + auth_url = f"{auth_url_base}?{urlencode(auth_url_params)}" + + context = { + "auth_url": auth_url + } + + return render(request, 'authentication/register-init.html', context) + + +def register_continue(request): + if request.user.is_authenticated: + raise PermissionDenied('You are already registered and logged in.') + + authorization_code = request.GET.get('code') + authorization_state = request.GET.get('state') + if not authorization_code: + return HttpResponseBadRequest() + if not authorization_state: + return HttpResponseBadRequest() + if not request.session.get('registration_sequence_token', None) == authorization_state: + return HttpResponseBadRequest() + + if request.method == 'GET': + # Fetch access and refresh tokens and save them to session storage + redirect_uri = request.build_absolute_uri(reverse('register-continue')) + response_data = withings.api.fetch_initial_tokens(authorization_code, redirect_uri) + if response_data['status'] != 0: + return HttpResponseBadRequest() + withings.api.save_tokens_to_session(request, response_data) + + user_form = CustomUserCreationForm() + profile_form = ProfileForm() + + else: + user_form = CustomUserCreationForm(request.POST) + profile_form = ProfileForm(request.POST) + + if user_form.is_valid() and profile_form.is_valid(): + user = user_form.save(commit=False) + profile = profile_form.save(commit=False) + profile.user = user + + user_password = request.POST.get('password1') + gotify_user_info = gotify.api.create_user(user.username, user_password) + gotify_app_info = gotify.api.create_application(user.username, user_password) + gotify.api.upload_application_picture(user.username, user_password, gotify_app_info['id']) + gotify_user = gotify.models.GotifyUser( + user=user, + id=gotify_user_info['id'] + ) + gotify_app = gotify.models.GotifyApplication( + user=gotify_user, + id=gotify_app_info['id'], + token=gotify_app_info['token'] + ) + + withings_api_account = withings.models.ApiAccount( + user=user, + userid=request.session.get('withings_userid') + ) + withings_access_token = withings.models.AccessToken( + account=withings_api_account, + value=request.session.get('withings_access_token'), + expires=parse_datetime(request.session.get('withings_access_token_expiry')) + ) + withings_refresh_token = withings.models.RefreshToken( + account=withings_api_account, + value=request.session.get('withings_refresh_token'), + expires=parse_datetime(request.session.get('withings_refresh_token_expiry')) + ) + + for instance in [ + user, profile, + gotify_user, gotify_app, + withings_api_account, withings_access_token, withings_refresh_token + ]: + instance.save() + + request.session.flush() + withings_api_account.update_records() + + return redirect('register-finalize') + + context = { + 'user_form': user_form, + 'profile_form': profile_form, + } + + return render(request, 'authentication/register-continue.html', context) + + +def register_finalize(request): + if request.user.is_authenticated: + raise PermissionDenied('You are already registered and logged in.') + + context = { + "gotify_public_url": settings.GOTIFY_CONFIG['PUBLIC_URL'] + } + + return render(request, 'authentication/register-finalize.html', context) diff --git a/app/core/README.md b/app/core/README.md new file mode 100644 index 0000000..3e89fba --- /dev/null +++ b/app/core/README.md @@ -0,0 +1,26 @@ +# Core + +This module is the main entrypoint for the application. +It provides global configuration variables and shared files. + +## Templates + +Shared template files are defined in [./templates/core/](./templates/core/). + +These include the base template, which all others inherit from, as well as the navigation bar and footer of the site. + +## Static files + +Static files are stored in [/app/static/](../app/static/) and served here by the webserver. + +**Warning:** files stored in this directory are publicly available. + +You can access static files in your Django template as follows: + +```html +{% load static %} + +... + +A Withings Scanwatch. +``` diff --git a/backend/database/__init__.py b/app/core/__init__.py similarity index 100% rename from backend/database/__init__.py rename to app/core/__init__.py diff --git a/app/core/asgi.py b/app/core/asgi.py new file mode 100644 index 0000000..3b35c6b --- /dev/null +++ b/app/core/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for core project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +application = get_asgi_application() diff --git a/app/core/settings.py b/app/core/settings.py new file mode 100644 index 0000000..77661a4 --- /dev/null +++ b/app/core/settings.py @@ -0,0 +1,142 @@ +""" +Django settings for core project. + +Generated by 'django-admin startproject' using Django 4.2.3. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +from pathlib import Path +from os import getenv + + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-s^q)z%f-7=1h5b00ctki2*-w=#3!k@p-#sq%=eajw)x2axx-e5' +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True +ALLOWED_HOSTS = [ + 'localhost', + '127.0.0.1', + '192.168.2.141' +] + + +# Application definition +INSTALLED_APPS = [ + 'widget_tweaks', + 'core', + 'authentication', + 'medwings', + 'withings', + 'gotify', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] +ROOT_URLCONF = 'core.urls' +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] +WSGI_APPLICATION = 'core.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': getenv('PG_NAME', 'medwings'), + 'USER': getenv('PG_USER', 'medwings'), + 'PASSWORD': getenv('PG_PASSWORD', 'medwings'), + 'HOST': getenv('PG_HOST', 'medwings-postgres'), + 'PORT': getenv('PG_PORT', '5432'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] +LOGIN_REDIRECT_URL = 'home' +LOGIN_URL = 'login' +LOGOUT_REDIRECT_URL = 'home' + + +# Internationalization +# https://docs.djangoproject.com/en/4.2/topics/i18n/ +LANGUAGE_CODE = 'en-us' +TIME_ZONE = getenv('TZ', 'Europe/Berlin') +USE_I18N = True +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.2/howto/static-files/ +STATIC_URL = 'static/' +STATICFILES_DIRS = [ + BASE_DIR / 'static', +] + + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +WITHINGS_CONFIG = { + 'CLIENT_ID': getenv('WITHINGS_CLIENT_ID'), + 'CLIENT_SECRET': getenv('WITHINGS_CLIENT_SECRET'), + 'ENDPOINT_URL_OAUTH2': 'https://wbsapi.withings.net/v2/oauth2' +} +GOTIFY_CONFIG = { + 'USERNAME': getenv('GOTIFY_USER'), + 'PASSWORD': getenv('GOTIFY_PASSWORD'), + 'HOST': getenv('GOTIFY_HOST'), + 'PUBLIC_URL': getenv('GOTIFY_PUBLIC_URL') +} diff --git a/app/core/templates/core/base.html b/app/core/templates/core/base.html new file mode 100644 index 0000000..7020cce --- /dev/null +++ b/app/core/templates/core/base.html @@ -0,0 +1,28 @@ +{% load static %} + + + + + + + {% block title %}Medwings{% endblock title %} + + + + + + + +
+ {% include 'core/navbar.html' %} +
+
+ {% block content %} + {% endblock content %} +
+
+ {% include 'core/footer.html' %} +
+ + + diff --git a/app/core/templates/core/footer.html b/app/core/templates/core/footer.html new file mode 100644 index 0000000..ea7ad65 --- /dev/null +++ b/app/core/templates/core/footer.html @@ -0,0 +1,3 @@ +
+

© 2023 Julian Lobbes

+
diff --git a/app/core/templates/core/navbar.html b/app/core/templates/core/navbar.html new file mode 100644 index 0000000..bcb1cd3 --- /dev/null +++ b/app/core/templates/core/navbar.html @@ -0,0 +1,55 @@ + + diff --git a/app/core/urls.py b/app/core/urls.py new file mode 100644 index 0000000..d79502e --- /dev/null +++ b/app/core/urls.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.urls import include, path + +urlpatterns = [ + path('', include('medwings.urls')), + path('auth/', include('authentication.urls')), +] diff --git a/app/core/wsgi.py b/app/core/wsgi.py new file mode 100644 index 0000000..f44964d --- /dev/null +++ b/app/core/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for core project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +application = get_wsgi_application() diff --git a/app/gotify/README.md b/app/gotify/README.md new file mode 100644 index 0000000..184afc4 --- /dev/null +++ b/app/gotify/README.md @@ -0,0 +1,8 @@ +# Gotify + +This module provides interfaces for a [Gotify Notfication Server](https://gotify.net/), +which allows the application to send push notifications to user's phones. + +Gotify is a separate microservice which allows users to subscribe to a notification channel. +The Gotify instance is created as a separate Docker container, and Medwings handles the creation +of users, applications and messages behind the scenes. diff --git a/backend/models/__init__.py b/app/gotify/__init__.py similarity index 100% rename from backend/models/__init__.py rename to app/gotify/__init__.py diff --git a/app/gotify/api.py b/app/gotify/api.py new file mode 100644 index 0000000..b5a82e3 --- /dev/null +++ b/app/gotify/api.py @@ -0,0 +1,75 @@ +import requests +from requests.auth import HTTPBasicAuth + +from django.conf import settings +from .models import GotifyUser + + +def create_user(username: str, password: str) -> dict: + """Creates a user on the Gotify instance. + + :param username: The name of the user to be created. + :param password: The password of the user to be created. + """ + + data = { + 'admin': False, + 'name': username, + 'pass': password + } + + response = requests.post( + url=f"http://{settings.GOTIFY_CONFIG['HOST']}/user", + auth=HTTPBasicAuth( + settings.GOTIFY_CONFIG['USERNAME'], + settings.GOTIFY_CONFIG['PASSWORD'], + ), + json=data + ) + + if response is not None: + response.raise_for_status() + + return response.json() + + +def create_application(username: str, password: str) -> dict: + """Creates an application on the Gotify instance. + + :param username: The user for whom an application will be created. + :param password: The user's password. + """ + + data = { + 'defaultPriority': 6, + 'description': 'A remote patient health monitoring system.', + 'name': 'Medwings' + } + + response = requests.post( + url=f"http://{settings.GOTIFY_CONFIG['HOST']}/application", + auth=HTTPBasicAuth(username, password), + json=data + ) + + if response is not None: + response.raise_for_status() + + return response.json() + + +def upload_application_picture(username: str, password: str, app_id: int): + with open('/app/static/medwings/images/logo/medwings-logo.png', 'rb') as image_file: + response = requests.post( + url=f"http://{settings.GOTIFY_CONFIG['HOST']}/application/{app_id}/image", + auth=HTTPBasicAuth( + username, + password, + ), + files={ + 'file': image_file + } + ) + + if response is not None: + response.raise_for_status() diff --git a/app/gotify/apps.py b/app/gotify/apps.py new file mode 100644 index 0000000..da4dfd6 --- /dev/null +++ b/app/gotify/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GotifyConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'gotify' diff --git a/backend/routes/__init__.py b/app/gotify/management/__init__.py similarity index 100% rename from backend/routes/__init__.py rename to app/gotify/management/__init__.py diff --git a/backend/schemas/__init__.py b/app/gotify/management/commands/__init__.py similarity index 100% rename from backend/schemas/__init__.py rename to app/gotify/management/commands/__init__.py diff --git a/app/gotify/management/commands/notify_all.py b/app/gotify/management/commands/notify_all.py new file mode 100644 index 0000000..d57e810 --- /dev/null +++ b/app/gotify/management/commands/notify_all.py @@ -0,0 +1,24 @@ +from django.urls import reverse +from django.core.management.base import BaseCommand + +from gotify import models + + +class Command(BaseCommand): + help = 'Notifies all users to take a vitals measurement now.' + + def handle(self, *args, **kwargs): + applications = models.GotifyApplication.objects.all() + + for application in applications: + message_text = f"Hello {application.user.user.first_name}. Please take your next vitals measurement now." + message_title = "Medwings Measurement Prompt" + url = reverse('mews-init') + + message = models.GotifyMessage( + message=message_text, + title=message_title, + url=url + ) + + application.send_message(message) diff --git a/app/gotify/migrations/0001_initial.py b/app/gotify/migrations/0001_initial.py new file mode 100644 index 0000000..caaa452 --- /dev/null +++ b/app/gotify/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.3 on 2023-07-30 21:15 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='GotifyUser', + fields=[ + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), + ('id', models.PositiveIntegerField(verbose_name='Gotify User ID')), + ], + ), + migrations.CreateModel( + name='GotifyApplication', + fields=[ + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='gotify.gotifyuser')), + ('id', models.PositiveIntegerField(verbose_name='Gotify Application ID')), + ('token', models.CharField(max_length=256, verbose_name='Gotify Application Token')), + ], + ), + ] diff --git a/app/gotify/migrations/__init__.py b/app/gotify/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/gotify/models.py b/app/gotify/models.py new file mode 100644 index 0000000..0f43d5e --- /dev/null +++ b/app/gotify/models.py @@ -0,0 +1,79 @@ +from enum import Enum +import json + +from django.db import models +from django.contrib.auth.models import User +from django.conf import settings +import requests + + +class GotifyUser(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) + id = models.PositiveIntegerField(verbose_name="Gotify User ID") + + +class GotifyMessageType(Enum): + plain = "text/plain" + markdown = "text/markdown" + + +class GotifyMessage(): + type: GotifyMessageType + message: str + title: str | None + priority: int + url: str | None + + def __init__(self, message: str, title: str | None = None, priority: int = 5, url: str | None = None, type: str = 'text/plain'): + self.message = message + self.title = title + if not 0 <= priority <= 10: + raise ValueError(f"Priority must be 0 to 10.") + self.priority = priority + self.url = url + self.type = GotifyMessageType(type) + + def as_dict(self) -> dict: + obj = { + "message": self.message, + "priority": self.priority, + "extras": { + "client::display": { + "contentType": self.type.value + } + } + } + + if self.title: + obj["title"] = self.title + + if self.url: + obj["extras"]["client::notification"] = { + "click": { + "url": self.url + } + } + + return obj + + +class GotifyApplication(models.Model): + user = models.OneToOneField(GotifyUser, on_delete=models.CASCADE, primary_key=True) + id = models.PositiveIntegerField(verbose_name="Gotify Application ID") + token = models.CharField(max_length=256, verbose_name="Gotify Application Token") + + def send_message(self, message: GotifyMessage): + endpoint_url = f"http://{settings.GOTIFY_CONFIG['HOST']}/message" + headers = { + "Authorization": f"Bearer {self.token}", + "Content-Type": "application/json" + } + + response = requests.post( + url=endpoint_url, + headers=headers, + data=json.dumps(message.as_dict()) + ) + + if response is not None: + response.raise_for_status() diff --git a/app/manage.py b/app/manage.py new file mode 100755 index 0000000..f2a662c --- /dev/null +++ b/app/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/app/medwings/README.md b/app/medwings/README.md new file mode 100644 index 0000000..afe90af --- /dev/null +++ b/app/medwings/README.md @@ -0,0 +1,16 @@ +# Medwings + +This module provides handles vitals data, MEWS calculations and the user interfaces for capturing and viewing the data. + +## MEWS + +The following vital signs need to be recorded for a MEWS calculation: + +* Heart Rate +* SPO2 +* Blood Pressure +* Body Temperature +* Respiration Rate + +A detailed explanation and formula +[can be found here](https://www.mdcalc.com/calc/1875/modified-early-warning-score-mews-clinical-deterioration#evidence). diff --git a/app/medwings/__init__.py b/app/medwings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/medwings/apps.py b/app/medwings/apps.py new file mode 100644 index 0000000..0a7fdee --- /dev/null +++ b/app/medwings/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MedwingsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'medwings' diff --git a/app/medwings/forms.py b/app/medwings/forms.py new file mode 100644 index 0000000..6c3d8d6 --- /dev/null +++ b/app/medwings/forms.py @@ -0,0 +1,18 @@ +from django import forms + +from medwings.models import Profile, RespirationScoreRecord + + +class ProfileForm(forms.ModelForm): + class Meta: + model = Profile + fields = ['date_of_birth', 'sex'] + widgets = { + 'date_of_birth': forms.DateInput(attrs={'type': 'date'}), + } + + +class RespirationScoreForm(forms.ModelForm): + class Meta: + model = RespirationScoreRecord + fields = ['value_severity'] diff --git a/app/medwings/migrations/0001_initial.py b/app/medwings/migrations/0001_initial.py new file mode 100644 index 0000000..5711d5c --- /dev/null +++ b/app/medwings/migrations/0001_initial.py @@ -0,0 +1,87 @@ +# Generated by Django 4.2.3 on 2023-07-30 21:15 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import medwings.validators + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='BloodPressureRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')), + ('value_systolic_mmhg', models.PositiveIntegerField(validators=[medwings.validators.BloodPressureRecordValidator.value_systolic_mmhg], verbose_name='Systolic Blood Pressure (mmhg)')), + ('value_diastolic_mmhg', models.PositiveIntegerField(validators=[medwings.validators.BloodPressureRecordValidator.value_diastolic_mmhg], verbose_name='Diastolic Blood Pressure (mmhg)')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='BodyTempRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')), + ('value_celsius', models.DecimalField(decimal_places=2, max_digits=5, unique=True, validators=[medwings.validators.BodyTempRecordValidator.value_celsius], verbose_name='Body Temperature (°C)')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='HeartRateRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')), + ('value_bpm', models.PositiveIntegerField(validators=[medwings.validators.HeartRateRecordValidator.value_bpm], verbose_name='Heart Rate (bpm)')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Profile', + fields=[ + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), + ('date_of_birth', models.DateField(validators=[medwings.validators.PersonValidator.date_of_birth], verbose_name='Date of birth')), + ('sex', models.CharField(choices=[('F', 'Female'), ('M', 'Male')], max_length=1, verbose_name='Sex assigned at birth')), + ], + ), + migrations.CreateModel( + name='Spo2LevelRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')), + ('value_percent', models.PositiveIntegerField(validators=[medwings.validators.Spo2LevelRecordValidator.value_percent], verbose_name='SPO2 (%)')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='RespirationScoreRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')), + ('value_severity', models.PositiveIntegerField(choices=[(0, 'No shortness of breath'), (1, 'A little shortness of breath'), (2, 'Severe shortness of breath')], validators=[medwings.validators.RespirationScoreRecordValidator.value_severity], verbose_name='Shortness Of Breath Severity')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='MewsRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was calculated')), + ('value_n', models.PositiveIntegerField(validators=[medwings.validators.MewsRecordValidator.value_n], verbose_name='Modified Early Warning Score')), + ('blood_pressure_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='medwings.bloodpressurerecord')), + ('body_temp_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='medwings.bodytemprecord')), + ('heart_rate_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='medwings.heartraterecord')), + ('respiration_score_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='medwings.respirationscorerecord')), + ('spo2_level_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='medwings.spo2levelrecord')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/app/medwings/migrations/__init__.py b/app/medwings/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/medwings/models.py b/app/medwings/models.py new file mode 100644 index 0000000..b362247 --- /dev/null +++ b/app/medwings/models.py @@ -0,0 +1,123 @@ +from django.db import models +from django.contrib.auth.models import User + +from . import validators + + +class Profile(models.Model): + SEX_FEMALE = "F" + SEX_MALE = "M" + SEX_CHOICES = [ + (SEX_FEMALE, "Female"), + (SEX_MALE, "Male") + ] + + user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) + date_of_birth = models.DateField(validators=[validators.PersonValidator.date_of_birth], verbose_name="Date of birth") + sex = models.CharField(max_length=1, choices=SEX_CHOICES, verbose_name="Sex assigned at birth") + + +class BloodPressureRecord(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + recorded = models.DateTimeField(validators=[validators.BloodPressureRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken") + value_systolic_mmhg = models.PositiveIntegerField(validators=[validators.BloodPressureRecordValidator.value_systolic_mmhg], verbose_name="Systolic Blood Pressure (mmhg)") + value_diastolic_mmhg = models.PositiveIntegerField(validators=[validators.BloodPressureRecordValidator.value_diastolic_mmhg], verbose_name="Diastolic Blood Pressure (mmhg)") + + +class BodyTempRecord(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + recorded = models.DateTimeField(validators=[validators.BodyTempRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken") + value_celsius = models.DecimalField(max_digits=5, decimal_places=2, validators=[validators.BodyTempRecordValidator.value_celsius], unique=True, verbose_name="Body Temperature (\u00B0C)") + + +class HeartRateRecord(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + recorded = models.DateTimeField(validators=[validators.HeartRateRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken") + value_bpm = models.PositiveIntegerField(validators=[validators.HeartRateRecordValidator.value_bpm], verbose_name="Heart Rate (bpm)") + + +class Spo2LevelRecord(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + recorded = models.DateTimeField(validators=[validators.Spo2LevelRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken") + value_percent = models.PositiveIntegerField(validators=[validators.Spo2LevelRecordValidator.value_percent], verbose_name="SPO2 (\u0025)") + + +class RespirationScoreRecord(models.Model): + SEVERITY_NONE = 0 + SEVERITY_LOW = 1 + SEVERITY_HIGH = 2 + SEVERITY_CHOICES = [ + (SEVERITY_NONE, "No shortness of breath"), + (SEVERITY_LOW, "A little shortness of breath"), + (SEVERITY_HIGH, "Severe shortness of breath"), + ] + + user = models.ForeignKey(User, on_delete=models.CASCADE) + recorded = models.DateTimeField(validators=[validators.RespirationScoreRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken") + value_severity = models.PositiveIntegerField(choices=SEVERITY_CHOICES, validators=[validators.RespirationScoreRecordValidator.value_severity], verbose_name="Shortness Of Breath Severity") + + +class MewsRecord(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + recorded = models.DateTimeField(validators=[validators.MewsRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was calculated") + value_n = models.PositiveIntegerField(validators=[validators.MewsRecordValidator.value_n], verbose_name="Modified Early Warning Score") + + blood_pressure_record = models.ForeignKey(BloodPressureRecord, on_delete=models.CASCADE) + body_temp_record = models.ForeignKey(BodyTempRecord, on_delete=models.CASCADE) + heart_rate_record = models.ForeignKey(HeartRateRecord, on_delete=models.CASCADE) + spo2_level_record = models.ForeignKey(Spo2LevelRecord, on_delete=models.CASCADE) + respiration_score_record = models.ForeignKey(RespirationScoreRecord, on_delete=models.CASCADE) + + + @staticmethod + def calculate_mews_value( + blood_pressure_systolic_value_mmhg: int, + body_temp_value_celsius: float, + heart_rate_value_bpm: int, + spo2_level_value_percent: int, + respiration_score_value_severity: int, + ): + mews_value = 0 + + if blood_pressure_systolic_value_mmhg <= 70: + mews_value += 3 + elif blood_pressure_systolic_value_mmhg <= 80: + mews_value += 2 + elif blood_pressure_systolic_value_mmhg <= 100: + mews_value += 1 + elif blood_pressure_systolic_value_mmhg < 200: + mews_value += 0 + else: + mews_value += 2 + + if heart_rate_value_bpm < 40: + mews_value += 2 + elif heart_rate_value_bpm <= 50: + mews_value += 1 + elif heart_rate_value_bpm <= 100: + mews_value += 0 + elif heart_rate_value_bpm <= 110: + mews_value += 1 + elif heart_rate_value_bpm < 130: + mews_value += 2 + else: + mews_value += 3 + + if respiration_score_value_severity == 1: + mews_value += 1 + elif respiration_score_value_severity == 2: + mews_value += 2 + + if spo2_level_value_percent < 90: + mews_value += 2 + elif spo2_level_value_percent < 95: + mews_value += 1 + + if body_temp_value_celsius < 35: + mews_value += 2 + elif body_temp_value_celsius <= 38.4: + mews_value += 0 + else: + mews_value += 2 + + return mews_value diff --git a/app/medwings/templates/medwings/dashboard.html b/app/medwings/templates/medwings/dashboard.html new file mode 100644 index 0000000..e28191a --- /dev/null +++ b/app/medwings/templates/medwings/dashboard.html @@ -0,0 +1,146 @@ +{% extends 'core/base.html' %} +{% load static %} +{% block title %} + Medwings | Dashboard +{% endblock title %} + +{% block content %} +
+ +

Dashboard

+

This page shows all of your recorded health data.

+ +
+

MEWS

+ {% if mews_data %} + + {% else %} +
+

There is no data yet

+
+ {% endif %} +
+ +
+

Blood Pressure

+ {% if blood_pressure_data %} + + {% else %} +
+

There is no data yet

+
+ {% endif %} +
+ +
+

Body Temperature

+ {% if body_temp_data %} + + {% else %} +
+

There is no data yet

+
+ {% endif %} +
+ +
+

Heart Rate

+ {% if heart_rate_data %} + + {% else %} +
+

There is no data yet

+
+ {% endif %} +
+ +
+

Blood Oxygenation

+ {% if spo2_level_data %} + + {% else %} +
+

There is no data yet

+
+ {% endif %} +
+ +
+

Respiration Score

+ {% if respiration_score_data %} + + {% else %} +
+

There is no data yet

+
+ {% endif %} +
+ +
+ + + + + +{% endblock content %} diff --git a/app/medwings/templates/medwings/index.html b/app/medwings/templates/medwings/index.html new file mode 100644 index 0000000..155dafd --- /dev/null +++ b/app/medwings/templates/medwings/index.html @@ -0,0 +1,56 @@ +{% extends 'core/base.html' %} +{% load static %} +{% block title %} + Medwings | Home +{% endblock title %} + +{% block content %} +
+
+ +
+

MedwingS

+
+
+

The Mobile Early Deterioration Warning System

+

+ We understand that after receiving medical care, you may still have concerns about your health, particularly if you're at + risk of sudden health changes. + That's where we come in. +

+

+ Our platform leverages smart medical sensor devices to keep track of your vital signs - such as heart rate, + blood pressure, and body temperature - providing you and your healthcare team with a detailed and continuous + picture of your health status. +

+
+ {% if not request.user.is_authenticated %} +

To use the platform, please log in. If you do not have an account yet, please register.

+ + {% else %} +

View your latest health data to stay up to date:

+ Go to your personal dashboard + {% endif %} +
+

+ Our unique feature is the ability to calculate your Modified Early Warning Score (MEWS) from your vitals data. + This system is used widely in healthcare settings to detect early signs of deterioration. + Now, it is available for you, right in the comfort of your home or on the go. +

+

+ Prompted by periodic reminders, you'll be asked to take measurements which will be sent automatically to our platform. + Here, we calculate your MEWS and generate alerts if we detect an increased risk of health deterioration. +

+

+ While we take care of your monitoring needs, you can enjoy your daily activities with peace of mind, knowing that a + dedicated team has your health in their sights. + Stay in control of your health with us, your personal health guardian. +

+

+ Welcome aboard! +

+
+{% endblock content %} diff --git a/app/medwings/templates/medwings/mews-continue.html b/app/medwings/templates/medwings/mews-continue.html new file mode 100644 index 0000000..e5ca8db --- /dev/null +++ b/app/medwings/templates/medwings/mews-continue.html @@ -0,0 +1,210 @@ +{% extends 'core/base.html' %} +{% load static %} +{% load widget_tweaks %} +{% block title %} + Medwings | Take a measurement +{% endblock title %} + +{% block content %} +
+

Record your health status

+
+

Please start measuring your vitals using your devices now.

+

Your measurement results will be synchronized automatically.

+ + +
+
+
+

Blood Pressure (systolic)

+
+
+ + +
+
+

Body Temperature

+
+
+ + +
+
+

Heart Rate

+
+
+ + +
+
+

Blood Oxygenation

+
+
+ + +
+
+

Respiration Score

+
+
+ + +
+
+

MEWS

+
+
+ +
+
+ +{% endblock content %} diff --git a/app/medwings/templates/medwings/mews-init.html b/app/medwings/templates/medwings/mews-init.html new file mode 100644 index 0000000..9977afd --- /dev/null +++ b/app/medwings/templates/medwings/mews-init.html @@ -0,0 +1,51 @@ +{% extends 'core/base.html' %} +{% load static %} +{% load widget_tweaks %} +{% block title %} + Medwings | Take a measurement +{% endblock title %} + +{% block content %} +
+

Record your health status

+
+
+ A Withings BPM core. + A Withings Thermo. + A Withings Scanwatch. +
+

Before you begin, please have your Withings devices ready for taking measurements.

+
+
+ {% csrf_token %} +
+ To get started, please answer the following question: + + {% if respiration_score_form.non_field_errors %} +
+ {% for error in respiration_score_form.non_field_errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} + + +
+ + {% render_field respiration_score_form.value_severity|add_error_class:"error" %} +
+ {% if respiration_score_form.value_severity.errors %} +
+ {% for error in respiration_score_form.value_severity.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} + + +
+
+
+{% endblock content %} diff --git a/app/medwings/urls.py b/app/medwings/urls.py new file mode 100644 index 0000000..7a2630a --- /dev/null +++ b/app/medwings/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.index, name="home"), + path("dashboard/", views.dashboard, name="dashboard"), + path("mews/init/", views.mews_init, name="mews-init"), + path("mews/continue/", views.mews_continue, name="mews-continue"), + path("mews/status/", views.mews_status, name="mews-status"), +] diff --git a/app/medwings/validators.py b/app/medwings/validators.py new file mode 100644 index 0000000..d940b15 --- /dev/null +++ b/app/medwings/validators.py @@ -0,0 +1,109 @@ +from datetime import date, datetime +from abc import ABC + +from django.core.exceptions import ValidationError + + +class DateValidator: + @staticmethod + def past_date(value: date, name: str = "date"): + if value > date.today(): + raise ValidationError(f"The {name} cannot be in the future.") + + @staticmethod + def past_datetime(value: datetime, name: str = "timestamp"): + if value > datetime.now(): + raise ValidationError(f"The {name} cannot be in the future.") + + +class PersonValidator: + @staticmethod + def date_of_birth(value): + DateValidator.past_date(value, "date of birth") + + +class AbstractRecordValidator(ABC): + @staticmethod + def recorded(value): + DateValidator.past_datetime(value, "time when the value was recorded") + + +class BloodPressureRecordValidator(AbstractRecordValidator): + MIN_VALUE_SYSTOLIC_MMHG = 0 + MAX_VALUE_SYSTOLIC_MMHG = 1000 + MIN_VALUE_DIASTOLIC_MMHG = 0 + MAX_VALUE_DIASTOLIC_MMHG = 1000 + + @staticmethod + def value_systolic_mmhg(value: int): + if value < BloodPressureRecordValidator.MIN_VALUE_SYSTOLIC_MMHG: + raise ValidationError(f"Systolic Blood Pressure cannot be below {BloodPressureRecordValidator.MIN_VALUE_SYSTOLIC_MMHG}") + if value > BloodPressureRecordValidator.MAX_VALUE_SYSTOLIC_MMHG: + raise ValidationError(f"Systolic Blood Pressure cannot be above {BloodPressureRecordValidator.MAX_VALUE_SYSTOLIC_MMHG}") + + @staticmethod + def value_diastolic_mmhg(value: int): + if value < BloodPressureRecordValidator.MIN_VALUE_DIASTOLIC_MMHG: + raise ValidationError(f"Diastolic Blood Pressure cannot be below {BloodPressureRecordValidator.MIN_VALUE_DIASTOLIC_MMHG}") + if value > BloodPressureRecordValidator.MAX_VALUE_DIASTOLIC_MMHG: + raise ValidationError(f"Diastolic Blood Pressure cannot be above {BloodPressureRecordValidator.MAX_VALUE_DIASTOLIC_MMHG}") + + +class BodyTempRecordValidator(AbstractRecordValidator): + MIN_VALUE_CELSIUS = 0 + MAX_VALUE_CELSIUS = 100 + + @staticmethod + def value_celsius(value: int): + if value < BodyTempRecordValidator.MIN_VALUE_CELSIUS: + raise ValidationError(f"Body Temperature cannot be below {BodyTempRecordValidator.MIN_VALUE_CELSIUS}") + if value > BodyTempRecordValidator.MAX_VALUE_CELSIUS: + raise ValidationError(f"Body Temperature cannot be above {BodyTempRecordValidator.MAX_VALUE_CELSIUS}") + + +class HeartRateRecordValidator(AbstractRecordValidator): + MIN_VALUE_BPM = 0 + MAX_VALUE_BPM = 1000 + + @staticmethod + def value_bpm(value: int): + if value < HeartRateRecordValidator.MIN_VALUE_BPM: + raise ValidationError(f"Heart Rate cannot be below {HeartRateRecordValidator.MIN_VALUE_BPM}") + if value > HeartRateRecordValidator.MAX_VALUE_BPM: + raise ValidationError(f"Heart Rate cannot be above {HeartRateRecordValidator.MAX_VALUE_BPM}") + + +class RespirationScoreRecordValidator(AbstractRecordValidator): + MIN_VALUE_SEVERITY = 0 + MAX_VALUE_SEVERITY = 2 + + @staticmethod + def value_severity(value: int): + if value < RespirationScoreRecordValidator.MIN_VALUE_SEVERITY: + raise ValidationError(f"Respiratory Inhibition Severity cannot be below {RespirationScoreRecordValidator.MIN_VALUE_SEVERITY}") + if value > RespirationScoreRecordValidator.MAX_VALUE_SEVERITY: + raise ValidationError(f"Respiratory Inhibition Severity cannot be above {RespirationScoreRecordValidator.MAX_VALUE_SEVERITY}") + + +class Spo2LevelRecordValidator(AbstractRecordValidator): + MIN_VALUE_PERCENT = 0 + MAX_VALUE_PERCENT = 100 + + @staticmethod + def value_percent(value: int): + if value < Spo2LevelRecordValidator.MIN_VALUE_PERCENT: + raise ValidationError(f"SPO2 cannot be below {Spo2LevelRecordValidator.MIN_VALUE_PERCENT}") + if value > Spo2LevelRecordValidator.MAX_VALUE_PERCENT: + raise ValidationError(f"SPO2 cannot be above {Spo2LevelRecordValidator.MAX_VALUE_PERCENT}") + + +class MewsRecordValidator(AbstractRecordValidator): + MIN_VALUE_N = 0 + MAX_VALUE_N = 100 + + @staticmethod + def value_n(value: int): + if value < MewsRecordValidator.MIN_VALUE_N: + raise ValidationError(f"MEWS cannot be below {MewsRecordValidator.MIN_VALUE_N}") + if value > MewsRecordValidator.MAX_VALUE_N: + raise ValidationError(f"MEWS cannot be above {MewsRecordValidator.MAX_VALUE_N}") diff --git a/app/medwings/views.py b/app/medwings/views.py new file mode 100644 index 0000000..7832929 --- /dev/null +++ b/app/medwings/views.py @@ -0,0 +1,117 @@ +from datetime import timedelta +import json + +from django.shortcuts import redirect, render +from django.http import HttpResponse, JsonResponse +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_http_methods +from django.utils import timezone + +from . import models +from . import forms + + +@require_http_methods(["GET"]) +def index(request): + return render(request, 'medwings/index.html') + + +@login_required +@require_http_methods(["GET"]) +def dashboard(request): + mews_records = models.MewsRecord.objects.filter(user=request.user) + blood_pressure_records = models.BloodPressureRecord.objects.filter(user=request.user) + body_temp_records = models.BodyTempRecord.objects.filter(user=request.user) + heart_rate_records = models.HeartRateRecord.objects.filter(user=request.user) + spo2_level_records = models.Spo2LevelRecord.objects.filter(user=request.user) + respiration_score_records = models.RespirationScoreRecord.objects.filter(user=request.user) + + context = { + "mews_data": json.dumps([{"x": record.recorded.timestamp(), "y": record.value_n} for record in mews_records]), + "blood_pressure_data": json.dumps([{"x": record.recorded.timestamp(), "y": record.value_systolic_mmhg} for record in blood_pressure_records]), + "body_temp_data": json.dumps([{"x": record.recorded.timestamp(), "y": float(record.value_celsius)} for record in body_temp_records]), + "heart_rate_data": json.dumps([{"x": record.recorded.timestamp(), "y": record.value_bpm} for record in heart_rate_records]), + "spo2_level_data": json.dumps([{"x": record.recorded.timestamp(), "y": record.value_percent} for record in spo2_level_records]), + "respiration_score_data": json.dumps([{"x": record.recorded.timestamp(), "y": record.value_severity} for record in respiration_score_records]), + } + + return render(request, 'medwings/dashboard.html', context) + + +@login_required +@require_http_methods(["GET", "POST"]) +def mews_init(request): + if request.method == 'POST': + respiration_score_form = forms.RespirationScoreForm(request.POST) + + if respiration_score_form.is_valid(): + respiration_score = respiration_score_form.save(commit=False) + respiration_score.recorded = timezone.now() + respiration_score.user = request.user + respiration_score.save() + + return redirect('mews-continue') + + else: + respiration_score_form = forms.RespirationScoreForm() + + context = { + 'respiration_score_form': respiration_score_form, + } + + return render(request, 'medwings/mews-init.html', context) + + +@login_required +def mews_continue(request): + return render(request, 'medwings/mews-continue.html') + + +@require_http_methods(["GET"]) +def mews_status(request): + if not request.user.is_authenticated: + return HttpResponse('Unauthorized', status=401) + + request.user.apiaccount.update_records() + + ten_minutes_ago = timezone.now() - timedelta(minutes=10) + blood_pressure_record = models.BloodPressureRecord.objects.filter(user=request.user).filter(recorded__gte=ten_minutes_ago).order_by('-recorded').first() + body_temp_record = models.BodyTempRecord.objects.filter(user=request.user).filter(recorded__gte=ten_minutes_ago).order_by('-recorded').first() + heart_rate_record = models.HeartRateRecord.objects.filter(user=request.user).filter(recorded__gte=ten_minutes_ago).order_by('-recorded').first() + spo2_level_record = models.Spo2LevelRecord.objects.filter(user=request.user).filter(recorded__gte=ten_minutes_ago).order_by('-recorded').first() + respiration_score_record = models.RespirationScoreRecord.objects.filter(user=request.user).filter(recorded__gte=ten_minutes_ago).order_by('-recorded').first() + + data = { + 'blood_pressure_value': blood_pressure_record.value_systolic_mmhg if blood_pressure_record else None, + 'body_temp_value': body_temp_record.value_celsius if body_temp_record else None, + 'heart_rate_value': heart_rate_record.value_bpm if heart_rate_record else None, + 'spo2_level_value': spo2_level_record.value_percent if spo2_level_record else None, + 'respiration_score_value': respiration_score_record.value_severity if respiration_score_record else None, + 'mews_value': None, + } + + if (blood_pressure_record + and body_temp_record + and heart_rate_record + and spo2_level_record + and respiration_score_record + ): + mews_record = models.MewsRecord( + user=request.user, recorded=timezone.now(), + value_n=models.MewsRecord.calculate_mews_value( + blood_pressure_record.value_systolic_mmhg, + body_temp_record.value_celsius, + heart_rate_record.value_bpm, + spo2_level_record.value_percent, + respiration_score_record.value_severity + ), + blood_pressure_record=blood_pressure_record, + body_temp_record=body_temp_record, + heart_rate_record=heart_rate_record, + respiration_score_record=respiration_score_record, + spo2_level_record=spo2_level_record + ) + mews_record.save() + data['mews_value'] = mews_record.value_n + + return JsonResponse(data) diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..72d30a5 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,14 @@ +asgiref==3.7.2 +certifi==2023.7.22 +charset-normalizer==3.2.0 +Django==4.2.3 +django-widget-tweaks==1.4.12 +djangorestframework==3.14.0 +idna==3.4 +psycopg==3.1.9 +psycopg-binary==3.1.9 +pytz==2023.3 +requests==2.31.0 +sqlparse==0.4.4 +typing_extensions==4.7.1 +urllib3==2.0.4 diff --git a/public/static/images/devices/36b60fc2-567f-4e7e-b76e-2a9006c0f0fb.webp b/app/static/medwings/images/devices/withings-bpm-core.webp similarity index 100% rename from public/static/images/devices/36b60fc2-567f-4e7e-b76e-2a9006c0f0fb.webp rename to app/static/medwings/images/devices/withings-bpm-core.webp diff --git a/public/static/images/devices/133184d2-3b7c-4589-bc76-9736455a56b9.webp b/app/static/medwings/images/devices/withings-scanwatch.webp similarity index 100% rename from public/static/images/devices/133184d2-3b7c-4589-bc76-9736455a56b9.webp rename to app/static/medwings/images/devices/withings-scanwatch.webp diff --git a/public/static/images/devices/3b0cd959-a293-488f-94d6-5f6dc0f775f3.webp b/app/static/medwings/images/devices/withings-thermo.webp similarity index 100% rename from public/static/images/devices/3b0cd959-a293-488f-94d6-5f6dc0f775f3.webp rename to app/static/medwings/images/devices/withings-thermo.webp diff --git a/app/static/medwings/images/logo/medwings-logo.png b/app/static/medwings/images/logo/medwings-logo.png new file mode 100644 index 0000000..0cc185a Binary files /dev/null and b/app/static/medwings/images/logo/medwings-logo.png differ diff --git a/app/static/medwings/images/logo/medwings-logo.svg b/app/static/medwings/images/logo/medwings-logo.svg new file mode 100644 index 0000000..fbc9c8c --- /dev/null +++ b/app/static/medwings/images/logo/medwings-logo.svg @@ -0,0 +1,154 @@ + + diff --git a/app/static/medwings/images/misc/hamburger.svg b/app/static/medwings/images/misc/hamburger.svg new file mode 100644 index 0000000..26452b8 --- /dev/null +++ b/app/static/medwings/images/misc/hamburger.svg @@ -0,0 +1,31 @@ + + + + + + + diff --git a/app/withings/README.md b/app/withings/README.md new file mode 100644 index 0000000..143c941 --- /dev/null +++ b/app/withings/README.md @@ -0,0 +1,235 @@ +# Withings + +This module provides interfaces used to communicate with the Withings Public API. + +## Smart Devices Used + +We use the following devices for vitals data measurement: + +* [Withings Scanwatch](https://www.withings.com/de/en/scanwatch) + * Heart Rate, SPO2 +* [Withings Thermo](https://www.withings.com/de/en/thermo) + * Body Surface Temperature +* [WIthings BPM Core](https://www.withings.com/de/en/bpm-core) + * Blood Pressure + +## API Access + +Data is gathered by taking measurements using the devices, either actively (BP sleeve, thermometer) or passively (smartwatch). +The devices are connected to the Withings mobile app. +The mobile app then regularly pushes gathered data to the Withings cloud. + +The Withings Dev Free plan allows for 120 API requests per minute. +Access to vitals data is available through the [Withings API](https://developer.withings.com/). + +A detailed [API integration guide](https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/public-health-data-api-overview/), +as well as an [API reference guide](https://developer.withings.com/api-reference) are available online. + +### Token expiry + +When the access token expires, HTTP status `200 OK` is returned, but the response body is as follows: + +```json +{ + "status": 401, + "body": {}, + "error": "XRequestID: Not provided invalid_token: The access token provided is invalid" +} +``` + +### Fetching health data + +Health records can be fetched via GET request as follows: + +```http +https://wbsapi.withings.net/measure?action=getmeas&meastypes=9,10,54,71,11 +``` + +The type of vitals measurement is mapped as follows: + +| Code | Type | Unit | +|------|--------------------------|------| +| 9 | Diastolic Blood Pressure | mmHg | +| 10 | Systolic Blood Pressure | mmHg | +| 11 | Heart Rate | bpm | +| 54 | SP02 | % | +| 71 | Body Temperature | °C | + +Note the `unit`-field in the response. +For body temperature, the `unit`-field has the value `-3`. +This means that to get the body temperature in °C, you must multiply the `value` by `10^(-3)`. + +The time of measurement can be parsed from the `measuregrps`'s `date` field. + +A successful response looks like so: + +```json +{ + "status": 0, + "body": { + "updatetime": 1690491663, + "timezone": "Europe/Berlin", + "measuregrps": [ + { + "grpid": 4716596696, + "attrib": 0, + "date": 1690491576, + "created": 1690491663, + "modified": 1690491663, + "category": 1, + "deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956", + "hash_deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956", + "measures": [ + { + "value": 89, + "type": 9, + "unit": 0, + "algo": 0, + "fm": 3 + }, + { + "value": 109, + "type": 10, + "unit": 0, + "algo": 0, + "fm": 3 + }, + { + "value": 88, + "type": 11, + "unit": 0, + "algo": 0, + "fm": 3 + } + ], + "modelid": 44, + "model": "BPM Core", + "comment": null + }, + { + "grpid": 4716596681, + "attrib": 0, + "date": 1690491236, + "created": 1690491662, + "modified": 1690491662, + "category": 1, + "deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956", + "hash_deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956", + "measures": [ + { + "value": 65, + "type": 9, + "unit": 0, + "algo": 0, + "fm": 3 + }, + { + "value": 92, + "type": 10, + "unit": 0, + "algo": 0, + "fm": 3 + }, + { + "value": 88, + "type": 11, + "unit": 0, + "algo": 0, + "fm": 3 + } + ], + "modelid": 44, + "model": "BPM Core", + "comment": null + }, + { + "grpid": 4712963495, + "attrib": 0, + "date": 1690375238, + "created": 1690375243, + "modified": 1690375243, + "category": 1, + "deviceid": "dbf7f61809d5fb350a16a50f6af6e826f0746082", + "hash_deviceid": "dbf7f61809d5fb350a16a50f6af6e826f0746082", + "measures": [ + { + "value": 99, + "type": 54, + "unit": 0, + "algo": 33619971, + "fm": 3, + "apppfmid": 9, + "appliver": 2741, + "algo_params": { + "1": 0, + "2": 15 + } + } + ], + "modelid": null, + "model": null, + "comment": null, + "is_inconclusive": false + }, + { + "grpid": 4712927310, + "attrib": 1, + "date": 1690374434, + "created": 1690374456, + "modified": 1690374486, + "category": 1, + "deviceid": "1d453daf947378fac40677e7a085eea73750b061", + "hash_deviceid": "1d453daf947378fac40677e7a085eea73750b061", + "measures": [ + { + "value": 37370, + "type": 71, + "unit": -3, + "algo": 0, + "fm": 0 + } + ], + "modelid": null, + "model": null, + "comment": null + }, + { + "grpid": 4712911433, + "attrib": 0, + "date": 1690373994, + "created": 1690374078, + "modified": 1690374078, + "category": 1, + "deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956", + "hash_deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956", + "measures": [ + { + "value": 88, + "type": 9, + "unit": 0, + "algo": 0, + "fm": 3 + }, + { + "value": 124, + "type": 10, + "unit": 0, + "algo": 0, + "fm": 3 + }, + { + "value": 70, + "type": 11, + "unit": 0, + "algo": 0, + "fm": 3 + } + ], + "modelid": null, + "model": null, + "comment": null + } + ] + } +} +``` diff --git a/app/withings/__init__.py b/app/withings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/withings/api.py b/app/withings/api.py new file mode 100644 index 0000000..8d4fdfc --- /dev/null +++ b/app/withings/api.py @@ -0,0 +1,116 @@ +from datetime import datetime, timedelta +from random import randint + +import requests +import pytz +from django.conf import settings +from django.utils import timezone +from django.contrib.auth.models import User +from urllib.parse import urlencode + +from medwings import models as mm + +def fetch_initial_tokens(authorization_code, redirect_uri): + data = { + 'action': 'requesttoken', + 'client_id': settings.WITHINGS_CONFIG['CLIENT_ID'], + 'client_secret': settings.WITHINGS_CONFIG['CLIENT_SECRET'], + 'grant_type': 'authorization_code', + 'code': authorization_code, + 'redirect_uri': redirect_uri + } + response = requests.post( + url=settings.WITHINGS_CONFIG['ENDPOINT_URL_OAUTH2'], + json=data + ) + if response is not None: + response.raise_for_status() + + return response.json() + + +def mock_fetch_initial_tokens(authorization_code, redirect_uri): + response = { + "status": 0, + "body": { + "userid": f"{randint(1, 5000)}", + "access_token": "a075f8c14fb8df40b08ebc8508533dc332a6910a", + "refresh_token": "f631236f02b991810feb774765b6ae8e6c6839ca", + "expires_in": 10800, + "scope": "user.info,user.metrics", + "csrf_token": "PACnnxwHTaBQOzF7bQqwFUUotIuvtzSM", + "token_type": "Bearer" + } + } + return response + + +def save_tokens_to_session(request, response_data): + request.session['withings_userid'] = response_data['body']['userid'] + request.session['withings_access_token'] = response_data['body']['access_token'] + request.session['withings_refresh_token'] = response_data['body']['refresh_token'] + + now = timezone.now() + request.session['withings_access_token_expiry'] = (now + timedelta(seconds=response_data['body']['expires_in'])).isoformat() + request.session['withings_refresh_token_expiry'] = (now + timedelta(days=365)).isoformat() + + +def parse_getmeas_response(response_body: dict, user: User) -> list: + body = response_body['body'] + records = [] + + timezone = pytz.timezone(body['timezone']) + + for measure_group in body['measuregrps']: + recorded = timezone.localize(datetime.fromtimestamp(measure_group['date'])) + + blood_pressure_systolic_value = None + blood_pressure_diastolic_value = None + body_temperature_value = None + heart_rate_value = None + spo2_level_value = None + for measure in measure_group['measures']: + measure_type = measure['type'] + measure_value = measure['value'] + measure_unit = measure['unit'] + + measure_value_adjusted = measure_value * (10 ** measure_unit) + + if measure_type == 9: + blood_pressure_diastolic_value = measure_value_adjusted + elif measure_type == 10: + blood_pressure_systolic_value = measure_value_adjusted + elif measure_type == 11: + heart_rate_value = measure_value_adjusted + elif measure_type == 54: + spo2_level_value = measure_value_adjusted + elif measure_type == 71: + body_temperature_value = measure_value_adjusted + + if blood_pressure_systolic_value and blood_pressure_diastolic_value: + records.append(mm.BloodPressureRecord( + user=user, + recorded=recorded, + value_systolic_mmhg=blood_pressure_systolic_value, + value_diastolic_mmhg=blood_pressure_diastolic_value + )) + if body_temperature_value: + records.append(mm.BodyTempRecord( + user=user, + recorded=recorded, + value_celsius=body_temperature_value + )) + if heart_rate_value: + records.append(mm.HeartRateRecord( + user=user, + recorded=recorded, + value_bpm=heart_rate_value + )) + if spo2_level_value: + records.append(mm.Spo2LevelRecord( + user=user, + recorded=recorded, + value_percent=spo2_level_value + )) + + return records diff --git a/app/withings/apps.py b/app/withings/apps.py new file mode 100644 index 0000000..d1847ce --- /dev/null +++ b/app/withings/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WithingsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'withings' diff --git a/app/withings/migrations/0001_initial.py b/app/withings/migrations/0001_initial.py new file mode 100644 index 0000000..c6e8861 --- /dev/null +++ b/app/withings/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.3 on 2023-07-30 21:15 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='ApiAccount', + fields=[ + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), + ('userid', models.PositiveIntegerField(verbose_name='Withings API User ID')), + ('last_update', models.DateTimeField(default=None, null=True, verbose_name='Time of last synchronization with Withings API')), + ], + ), + migrations.CreateModel( + name='AccessToken', + fields=[ + ('account', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='withings.apiaccount')), + ('value', models.CharField(max_length=256, verbose_name='Withings API Access Token')), + ('expires', models.DateTimeField(verbose_name='Time of expiration')), + ], + ), + migrations.CreateModel( + name='RefreshToken', + fields=[ + ('account', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='withings.apiaccount')), + ('value', models.CharField(max_length=256, verbose_name='Withings API Refresh Token')), + ('expires', models.DateTimeField(verbose_name='Time of expiration')), + ], + ), + ] diff --git a/app/withings/migrations/__init__.py b/app/withings/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/withings/models.py b/app/withings/models.py new file mode 100644 index 0000000..23d35d3 --- /dev/null +++ b/app/withings/models.py @@ -0,0 +1,90 @@ +from datetime import datetime, timedelta + +from django.db import models, IntegrityError +from django.contrib.auth.models import User +from django.conf import settings +from django.utils import timezone +import requests + +from . import api + + +class AccessToken(models.Model): + account = models.OneToOneField("ApiAccount", on_delete=models.CASCADE, primary_key=True) + value = models.CharField(max_length=256, verbose_name="Withings API Access Token") + expires = models.DateTimeField(verbose_name="Time of expiration") + + +class RefreshToken(models.Model): + account = models.OneToOneField("ApiAccount", on_delete=models.CASCADE, primary_key=True) + value = models.CharField(max_length=256, verbose_name="Withings API Refresh Token") + expires = models.DateTimeField(verbose_name="Time of expiration") + + +class ApiAccount(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) + userid = models.PositiveIntegerField(verbose_name="Withings API User ID") + last_update = models.DateTimeField(null=True, default=None, verbose_name="Time of last synchronization with Withings API") + + def refresh_tokens(self): + data = { + 'action': 'requesttoken', + 'client_id': settings.WITHINGS_CONFIG['CLIENT_ID'], + 'client_secret': settings.WITHINGS_CONFIG['CLIENT_SECRET'], + 'grant_type': 'refresh_token', + 'refresh_token': self.refreshtoken.value + } + response = requests.post( + url=settings.WITHINGS_CONFIG['ENDPOINT_URL_OAUTH2'], + json=data + ) + + if response is not None: + response.raise_for_status() + response_data = response.json() + + now = timezone.now() + self.accesstoken.value = response_data['body']['access_token'] + self.accesstoken.expires = now + timedelta(seconds=response_data['body']['expires_in']) + self.refreshtoken.value = response_data['body']['refresh_token'] + self.refreshtoken.expires = now + timedelta(days=365) + self.accesstoken.save() + self.refreshtoken.save() + + + def get_measurements(self, since: datetime | None = None) -> list: + if self.accesstoken.expires < timezone.now(): + self.refresh_tokens() + + params={ + 'action': 'getmeas', + 'meastypes': '9,10,11,54,71' + } + if since: + params['lastupdate'] = str(int(since.timestamp())) + + response = requests.get( + url="https://wbsapi.withings.net/measure", + params=params, + headers={ + 'Authorization': f"Bearer {self.accesstoken.value}" + } + ) + if response is not None: + response.raise_for_status() + data = response.json() + if data['status'] != 0: + raise RuntimeError(f"Received status {data['status']} while retrieving measurements: {data['error']}") + + return api.parse_getmeas_response(data, self.user) + + + def update_records(self): + records = self.get_measurements(self.last_update) + for record in records: + try: + record.save() + except IntegrityError: + pass + self.last_update = timezone.now() + self.save() diff --git a/frontend/src/css/fonts.css b/assets/css/fonts.css similarity index 53% rename from frontend/src/css/fonts.css rename to assets/css/fonts.css index 12cd87e..b145627 100644 --- a/frontend/src/css/fonts.css +++ b/assets/css/fonts.css @@ -1,109 +1,109 @@ /* Kanit */ @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-thin.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-thin.ttf') format('truetype'); font-weight: 100; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-thin-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-thin-italic.ttf') format('truetype'); font-weight: 100; font-style: italic; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-extralight.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-extralight.ttf') format('truetype'); font-weight: 200; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-extralight-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-extralight-italic.ttf') format('truetype'); font-weight: 200; font-style: italic; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-light.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-light.ttf') format('truetype'); font-weight: 300; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-light-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-light-italic.ttf') format('truetype'); font-weight: 300; font-style: italic; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-regular.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-regular-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-regular-italic.ttf') format('truetype'); font-weight: 400; font-style: italic; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-medium.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-medium.ttf') format('truetype'); font-weight: 500; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-medium-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-medium-italic.ttf') format('truetype'); font-weight: 500; font-style: italic; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-semibold.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-semibold.ttf') format('truetype'); font-weight: 600; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-semibold-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-semibold-italic.ttf') format('truetype'); font-weight: 600; font-style: italic; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-bold.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-bold.ttf') format('truetype'); font-weight: 700; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-bold-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-bold-italic.ttf') format('truetype'); font-weight: 700; font-style: italic; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-extrabold.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-extrabold.ttf') format('truetype'); font-weight: 800; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-extrabold-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-extrabold-italic.ttf') format('truetype'); font-weight: 800; font-style: italic; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-black.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-black.ttf') format('truetype'); font-weight: 900; font-style: normal; } @font-face { font-family: 'Kanit'; - src: url('/frontend/assets/fonts/kanit/kanit-black-italic.ttf') format('truetype'); + src: url('/assets/fonts/kanit/kanit-black-italic.ttf') format('truetype'); font-weight: 900; font-style: italic; } @@ -112,109 +112,109 @@ /* Montserrat */ @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-thin.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-thin.ttf') format('truetype'); font-weight: 100; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-thin-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-thin-italic.ttf') format('truetype'); font-weight: 100; font-style: italic; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-extralight.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-extralight.ttf') format('truetype'); font-weight: 200; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-extralight-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-extralight-italic.ttf') format('truetype'); font-weight: 200; font-style: italic; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-light.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-light.ttf') format('truetype'); font-weight: 300; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-light-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-light-italic.ttf') format('truetype'); font-weight: 300; font-style: italic; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-regular.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-regular-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-regular-italic.ttf') format('truetype'); font-weight: 400; font-style: italic; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-medium.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-medium.ttf') format('truetype'); font-weight: 500; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-medium-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-medium-italic.ttf') format('truetype'); font-weight: 500; font-style: italic; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-semibold.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-semibold.ttf') format('truetype'); font-weight: 600; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-semibold-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-semibold-italic.ttf') format('truetype'); font-weight: 600; font-style: italic; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-bold.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-bold.ttf') format('truetype'); font-weight: 700; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-bold-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-bold-italic.ttf') format('truetype'); font-weight: 700; font-style: italic; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-extrabold.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-extrabold.ttf') format('truetype'); font-weight: 800; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-extrabold-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-extrabold-italic.ttf') format('truetype'); font-weight: 800; font-style: italic; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-black.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-black.ttf') format('truetype'); font-weight: 900; font-style: normal; } @font-face { font-family: 'Montserrat'; - src: url('/frontend/assets/fonts/montserrat/montserrat-black-italic.ttf') format('truetype'); + src: url('/assets/fonts/montserrat/montserrat-black-italic.ttf') format('truetype'); font-weight: 900; font-style: italic; } @@ -223,97 +223,97 @@ /* SourceCodePro */ @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-extralight.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-extralight.ttf') format('truetype'); font-weight: 200; font-style: normal; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-extralight-italic.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-extralight-italic.ttf') format('truetype'); font-weight: 200; font-style: italic; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-light.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-light.ttf') format('truetype'); font-weight: 300; font-style: normal; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-light-italic.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-light-italic.ttf') format('truetype'); font-weight: 300; font-style: italic; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-regular.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-regular-italic.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-regular-italic.ttf') format('truetype'); font-weight: 400; font-style: italic; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-medium.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-medium.ttf') format('truetype'); font-weight: 500; font-style: normal; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-medium-italic.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-medium-italic.ttf') format('truetype'); font-weight: 500; font-style: italic; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-semibold.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-semibold.ttf') format('truetype'); font-weight: 600; font-style: normal; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-semibold-italic.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-semibold-italic.ttf') format('truetype'); font-weight: 600; font-style: italic; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-bold.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-bold.ttf') format('truetype'); font-weight: 700; font-style: normal; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-bold-italic.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-bold-italic.ttf') format('truetype'); font-weight: 700; font-style: italic; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-extrabold.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-extrabold.ttf') format('truetype'); font-weight: 800; font-style: normal; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-extrabold-italic.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-extrabold-italic.ttf') format('truetype'); font-weight: 800; font-style: italic; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-black.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-black.ttf') format('truetype'); font-weight: 900; font-style: normal; } @font-face { font-family: 'SourceCodePro'; - src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-black-italic.ttf') format('truetype'); + src: url('/assets/fonts/sourcecodepro/sourcecodepro-black-italic.ttf') format('truetype'); font-weight: 900; font-style: italic; } @@ -322,58 +322,88 @@ /* Lora */ @font-face { font-family: 'Lora'; - src: url('/frontend/assets/fonts/lora/lora-regular.ttf') format('truetype'); + src: url('/assets/fonts/lora/lora-regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Lora'; - src: url('/frontend/assets/fonts/lora/lora-regular-italic.ttf') format('truetype'); + src: url('/assets/fonts/lora/lora-regular-italic.ttf') format('truetype'); font-weight: 400; font-style: italic; } @font-face { font-family: 'Lora'; - src: url('/frontend/assets/fonts/lora/lora-medium.ttf') format('truetype'); + src: url('/assets/fonts/lora/lora-medium.ttf') format('truetype'); font-weight: 500; font-style: normal; } @font-face { font-family: 'Lora'; - src: url('/frontend/assets/fonts/lora/lora-medium-italic.ttf') format('truetype'); + src: url('/assets/fonts/lora/lora-medium-italic.ttf') format('truetype'); font-weight: 500; font-style: italic; } @font-face { font-family: 'Lora'; - src: url('/frontend/assets/fonts/lora/lora-semibold.ttf') format('truetype'); + src: url('/assets/fonts/lora/lora-semibold.ttf') format('truetype'); font-weight: 600; font-style: normal; } @font-face { font-family: 'Lora'; - src: url('/frontend/assets/fonts/lora/lora-semibold-italic.ttf') format('truetype'); + src: url('/assets/fonts/lora/lora-semibold-italic.ttf') format('truetype'); font-weight: 600; font-style: italic; } @font-face { font-family: 'Lora'; - src: url('/frontend/assets/fonts/lora/lora-bold.ttf') format('truetype'); + src: url('/assets/fonts/lora/lora-bold.ttf') format('truetype'); font-weight: 700; font-style: normal; } @font-face { font-family: 'Lora'; - src: url('/frontend/assets/fonts/lora/lora-bold-italic.ttf') format('truetype'); + src: url('/assets/fonts/lora/lora-bold-italic.ttf') format('truetype'); font-weight: 700; font-style: italic; } -/* NotoColorEmoji */ +/* PlayfairDisplaySC */ @font-face { - font-family: 'NotoColorEmoji'; - src: url('/frontend/assets/fonts/notocoloremoji/notocoloremoji-regular.ttf') format('truetype'); + font-family: 'PlayfairDisplaySC'; + src: url('/assets/fonts/playfairdisplaysc/playfairdisplaysc-regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } +@font-face { + font-family: 'PlayfairDisplaySC'; + src: url('/assets/fonts/playfairdisplaysc/playfairdisplaysc-regular-italic.ttf') format('truetype'); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: 'PlayfairDisplaySC'; + src: url('/assets/fonts/playfairdisplaysc/playfairdisplaysc-bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: 'PlayfairDisplaySC'; + src: url('/assets/fonts/playfairdisplaysc/playfairdisplaysc-bold-italic.ttf') format('truetype'); + font-weight: 700; + font-style: italic; +} +@font-face { + font-family: 'PlayfairDisplaySC'; + src: url('/assets/fonts/playfairdisplaysc/playfairdisplaysc-black.ttf') format('truetype'); + font-weight: 900; + font-style: normal; +} +@font-face { + font-family: 'PlayfairDisplaySC'; + src: url('/assets/fonts/playfairdisplaysc/playfairdisplaysc-black-italic.ttf') format('truetype'); + font-weight: 900; + font-style: italic; +} diff --git a/assets/css/styles.css b/assets/css/styles.css new file mode 100644 index 0000000..4a58eeb --- /dev/null +++ b/assets/css/styles.css @@ -0,0 +1,363 @@ +@import "./fonts.css"; +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; + +h1, h2, h3, h4, h5, h6 { + font-family: Kanit; + font-weight: 600; + font-style: normal; + text-align: center; + line-height: 1; +} + +h1 { + font-size: 3rem; + @apply text-primary-200; +} + +h1.title { + @apply font-title font-bold; + @apply underline text-primary-200/90; +} + +h2 { + font-size: 2.6rem; +} + +h3 { + font-size: 2.2rem; +} + +h4 { + font-size: 1.8rem; +} + +h5 { + font-size: 1.4rem; +} + +h6 { + font-size: 1rem; +} + +p { + @apply text-center sm:text-start; +} + +input { + @apply rounded rounded-md drop-shadow-sm px-2 py-1; + @apply bg-secondary/25; + @apply w-full; +} + +input[type="submit"] { + @apply rounded rounded-lg drop-shadow-md px-4 py-2; + @apply bg-secondary hover:bg-secondary-600; + @apply cursor-pointer; + @apply font-semibold; + @apply hover:drop-shadow-xl; +} + +select { + @apply rounded rounded-md drop-shadow-sm px-2 py-1; + @apply bg-secondary/25; + @apply w-full; +} + +select.error { + @apply border-2 border-failure/50; +} + +input.error { + @apply border-2 border-failure/50; +} + +fieldset { + @apply border border-secondary p-4; +} +legend { + @apply text-sm text-secondary px-2; +} +label { + @apply text-sm text-secondary/75 font-semibold; +} + +a:not(.btn, .btn-outline) { + @apply underline text-secondary-300; +} +div.call-to-action-box a { + @apply text-secondary-200; +} + +ul, ol { + list-style-position: outside; +} +ul { + list-style-type: disc; +} +ol { + list-style-type: decimal; +} +li { + @apply text-start; +} + +code { + @apply bg-neutral-800/75 rounded-md; + @apply px-1 py-0.5; + @apply text-neutral-200 font-mono; +} + +.btn { + text-align: center; + @apply rounded rounded-lg drop-shadow-md px-2 py-1; + @apply bg-accent hover:bg-accent-600; + @apply font-semibold text-primary-100 hover:text-primary-200; + @apply hover:drop-shadow-xl; + @apply border-2 border-accent hover:border-accent-700; + @apply transition-all; +} + +.btn-outline { + text-align: center; + @apply rounded rounded-lg drop-shadow-md px-2 py-1; + @apply bg-transparent hover:bg-accent-600; + @apply font-semibold text-accent hover:text-primary-100; + @apply hover:drop-shadow-xl; + @apply border-2 border-accent hover:border-accent-700; + @apply transition-all; +} + +body.global { + min-height: 100vh; + width: 100%; + display: flex; + flex-direction: column; + @apply bg-gradient-to-b from-primary-900/75 via-background to-primary-900/75; +} + +header.global, main.global, footer.global { + width: 100%; +} + +main.global { + display: flex; + flex-direction: column; + flex-grow: 1; + justify-content: center; + align-items: center; +} + +div.status-message { + display: flex; + width: 100%; + justify-content: center; + align-items: center; + border-radius: 0.5rem; + padding: 0.5rem; +} + +div.status-message.error { + @apply italic text-failure-300 bg-failure/50 border border-failure; +} + +div.call-to-action-box { + @apply bg-gradient-to-r from-secondary-300/75 to-secondary-500/75; + @apply rounded-md py-4 px-6; + @apply text-center sm:text-start; +} + +.loader { + overflow: hidden; +} +.loader.loader-bar { + @apply h-1 bg-accent rounded rounded-full; +} +.loader.loader-value { + @apply font-bold text-end; +} + +.loader--loading { + animation: loading 2s infinite linear; +} +.loader--finished { + animation: finished 1.5s ease-in both; +} +.loader--value { + animation: loaded 1s ease-out both; +} + +@keyframes loading { + 0% { + transform: scaleX(0%) translateX(-100%); + transform-origin: left; + } + + 50% { + transform: scaleX(50%); + transform-origin: center; + } + + 100% { + transform: scaleX(0%) translateX(100%); + transform-origin: right; + } +} + +@keyframes finished { + 0% { + transform: scaleX(0%); + transform-origin: center; + } + + 50% { + transform: scaleX(100%); + transform-origin: center; + opacity: 1; + } + + 100% { + transform: scaleX(100%) translateX(1000%); + transform-origin: right; + opacity: 0; + } +} + +@keyframes loaded { + 0% { + transform: translateX(1000%); + opacity: 0; + } + + 100% { + transform: translateX(0%); + opacity: 1; + } +} + +#logo-wings { + animation: logowings 2s ease-in-out infinite alternate; +} +#logo-wings--subtle { + animation: logowings-subtle 2s ease-in-out infinite alternate; +} +#logo-heart { + animation: logoheart 1s linear infinite; + transform-origin: center; +} +#logo-heart--subtle { + animation: logoheart-subtle 1s linear infinite; + transform-origin: center; +} +#logo-blips { + animation: logoblips 6s linear infinite; + transform-origin: bottom; + transform-box: fill-box; +} +#logo-blips--subtle { + animation: logoblips-subtle 6s linear infinite; + transform-origin: bottom; + transform-box: fill-box; +} + +@keyframes logowings { + 0% { + transform: translateY(0%); + } + + 100% { + transform: translateY(5%); + } +} +@keyframes logowings-subtle { + 0% { + transform: translateY(5%); + } + + 100% { + transform: translateY(8%); + } +} + +@keyframes logoheart { + 0%, 25%, 50%, 100% { + transform: scale(100%); + } + + 12%, 37% { + transform: scale(105%); + } +} +@keyframes logoheart-subtle { + 0%, 25%, 50%, 100% { + transform: scale(100%); + } + + 12%, 37% { + transform: scale(102%); + } +} + +@keyframes logoblips { + 0% { + transform: rotateY(0deg) + } + + 5%, 100% { + transform: rotateY(360deg); + } +} +@keyframes logoblips-subtle { + 0% { + transform: rotateY(0deg) + } + + 5%, 100% { + transform: rotateY(360deg); + } +} + +path.hamburger-path { + @apply fill-secondary stroke-secondary; +} + +#hamburger-content.hamburger--closed { + @apply hidden sm:flex; +} +#hamburger-content.hamburger--open { + @apply flex; +} +#hamburger-svg.hamburger--open { + transform-origin: center; + transform: rotate(90deg); +} +#hamburger-svg.hamburger--closed { + transform-origin: center; + transform: rotate(0deg); +} + +.help-div--changing p.fadeout { + animation: 1s fadeout ease-in-out both; +} +.help-div--changing p.fadein, a.fadein { + animation: 1s fadein ease-in-out 1s both; +} +@keyframes fadeout { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} +@keyframes fadein { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} diff --git a/frontend/assets/fonts/kanit/kanit-black-italic.ttf b/assets/fonts/kanit/kanit-black-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-black-italic.ttf rename to assets/fonts/kanit/kanit-black-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-black.ttf b/assets/fonts/kanit/kanit-black.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-black.ttf rename to assets/fonts/kanit/kanit-black.ttf diff --git a/frontend/assets/fonts/kanit/kanit-bold-italic.ttf b/assets/fonts/kanit/kanit-bold-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-bold-italic.ttf rename to assets/fonts/kanit/kanit-bold-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-bold.ttf b/assets/fonts/kanit/kanit-bold.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-bold.ttf rename to assets/fonts/kanit/kanit-bold.ttf diff --git a/frontend/assets/fonts/kanit/kanit-extrabold-italic.ttf b/assets/fonts/kanit/kanit-extrabold-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-extrabold-italic.ttf rename to assets/fonts/kanit/kanit-extrabold-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-extrabold.ttf b/assets/fonts/kanit/kanit-extrabold.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-extrabold.ttf rename to assets/fonts/kanit/kanit-extrabold.ttf diff --git a/frontend/assets/fonts/kanit/kanit-extralight-italic.ttf b/assets/fonts/kanit/kanit-extralight-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-extralight-italic.ttf rename to assets/fonts/kanit/kanit-extralight-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-extralight.ttf b/assets/fonts/kanit/kanit-extralight.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-extralight.ttf rename to assets/fonts/kanit/kanit-extralight.ttf diff --git a/frontend/assets/fonts/kanit/kanit-light-italic.ttf b/assets/fonts/kanit/kanit-light-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-light-italic.ttf rename to assets/fonts/kanit/kanit-light-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-light.ttf b/assets/fonts/kanit/kanit-light.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-light.ttf rename to assets/fonts/kanit/kanit-light.ttf diff --git a/frontend/assets/fonts/kanit/kanit-medium-italic.ttf b/assets/fonts/kanit/kanit-medium-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-medium-italic.ttf rename to assets/fonts/kanit/kanit-medium-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-medium.ttf b/assets/fonts/kanit/kanit-medium.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-medium.ttf rename to assets/fonts/kanit/kanit-medium.ttf diff --git a/frontend/assets/fonts/kanit/kanit-regular-italic.ttf b/assets/fonts/kanit/kanit-regular-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-regular-italic.ttf rename to assets/fonts/kanit/kanit-regular-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-regular.ttf b/assets/fonts/kanit/kanit-regular.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-regular.ttf rename to assets/fonts/kanit/kanit-regular.ttf diff --git a/frontend/assets/fonts/kanit/kanit-semibold-italic.ttf b/assets/fonts/kanit/kanit-semibold-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-semibold-italic.ttf rename to assets/fonts/kanit/kanit-semibold-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-semibold.ttf b/assets/fonts/kanit/kanit-semibold.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-semibold.ttf rename to assets/fonts/kanit/kanit-semibold.ttf diff --git a/frontend/assets/fonts/kanit/kanit-thin-italic.ttf b/assets/fonts/kanit/kanit-thin-italic.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-thin-italic.ttf rename to assets/fonts/kanit/kanit-thin-italic.ttf diff --git a/frontend/assets/fonts/kanit/kanit-thin.ttf b/assets/fonts/kanit/kanit-thin.ttf similarity index 100% rename from frontend/assets/fonts/kanit/kanit-thin.ttf rename to assets/fonts/kanit/kanit-thin.ttf diff --git a/frontend/assets/fonts/lora/lora-bold-italic.ttf b/assets/fonts/lora/lora-bold-italic.ttf similarity index 100% rename from frontend/assets/fonts/lora/lora-bold-italic.ttf rename to assets/fonts/lora/lora-bold-italic.ttf diff --git a/frontend/assets/fonts/lora/lora-bold.ttf b/assets/fonts/lora/lora-bold.ttf similarity index 100% rename from frontend/assets/fonts/lora/lora-bold.ttf rename to assets/fonts/lora/lora-bold.ttf diff --git a/frontend/assets/fonts/lora/lora-medium-italic.ttf b/assets/fonts/lora/lora-medium-italic.ttf similarity index 100% rename from frontend/assets/fonts/lora/lora-medium-italic.ttf rename to assets/fonts/lora/lora-medium-italic.ttf diff --git a/frontend/assets/fonts/lora/lora-medium.ttf b/assets/fonts/lora/lora-medium.ttf similarity index 100% rename from frontend/assets/fonts/lora/lora-medium.ttf rename to assets/fonts/lora/lora-medium.ttf diff --git a/frontend/assets/fonts/lora/lora-regular-italic.ttf b/assets/fonts/lora/lora-regular-italic.ttf similarity index 100% rename from frontend/assets/fonts/lora/lora-regular-italic.ttf rename to assets/fonts/lora/lora-regular-italic.ttf diff --git a/frontend/assets/fonts/lora/lora-regular.ttf b/assets/fonts/lora/lora-regular.ttf similarity index 100% rename from frontend/assets/fonts/lora/lora-regular.ttf rename to assets/fonts/lora/lora-regular.ttf diff --git a/frontend/assets/fonts/lora/lora-semibold-italic.ttf b/assets/fonts/lora/lora-semibold-italic.ttf similarity index 100% rename from frontend/assets/fonts/lora/lora-semibold-italic.ttf rename to assets/fonts/lora/lora-semibold-italic.ttf diff --git a/frontend/assets/fonts/lora/lora-semibold.ttf b/assets/fonts/lora/lora-semibold.ttf similarity index 100% rename from frontend/assets/fonts/lora/lora-semibold.ttf rename to assets/fonts/lora/lora-semibold.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-black-italic.ttf b/assets/fonts/montserrat/montserrat-black-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-black-italic.ttf rename to assets/fonts/montserrat/montserrat-black-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-black.ttf b/assets/fonts/montserrat/montserrat-black.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-black.ttf rename to assets/fonts/montserrat/montserrat-black.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-bold-italic.ttf b/assets/fonts/montserrat/montserrat-bold-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-bold-italic.ttf rename to assets/fonts/montserrat/montserrat-bold-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-bold.ttf b/assets/fonts/montserrat/montserrat-bold.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-bold.ttf rename to assets/fonts/montserrat/montserrat-bold.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-extrabold-italic.ttf b/assets/fonts/montserrat/montserrat-extrabold-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-extrabold-italic.ttf rename to assets/fonts/montserrat/montserrat-extrabold-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-extrabold.ttf b/assets/fonts/montserrat/montserrat-extrabold.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-extrabold.ttf rename to assets/fonts/montserrat/montserrat-extrabold.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-extralight-italic.ttf b/assets/fonts/montserrat/montserrat-extralight-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-extralight-italic.ttf rename to assets/fonts/montserrat/montserrat-extralight-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-extralight.ttf b/assets/fonts/montserrat/montserrat-extralight.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-extralight.ttf rename to assets/fonts/montserrat/montserrat-extralight.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-light-italic.ttf b/assets/fonts/montserrat/montserrat-light-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-light-italic.ttf rename to assets/fonts/montserrat/montserrat-light-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-light.ttf b/assets/fonts/montserrat/montserrat-light.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-light.ttf rename to assets/fonts/montserrat/montserrat-light.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-medium-italic.ttf b/assets/fonts/montserrat/montserrat-medium-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-medium-italic.ttf rename to assets/fonts/montserrat/montserrat-medium-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-medium.ttf b/assets/fonts/montserrat/montserrat-medium.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-medium.ttf rename to assets/fonts/montserrat/montserrat-medium.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-regular-italic.ttf b/assets/fonts/montserrat/montserrat-regular-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-regular-italic.ttf rename to assets/fonts/montserrat/montserrat-regular-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-regular.ttf b/assets/fonts/montserrat/montserrat-regular.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-regular.ttf rename to assets/fonts/montserrat/montserrat-regular.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-semibold-italic.ttf b/assets/fonts/montserrat/montserrat-semibold-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-semibold-italic.ttf rename to assets/fonts/montserrat/montserrat-semibold-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-semibold.ttf b/assets/fonts/montserrat/montserrat-semibold.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-semibold.ttf rename to assets/fonts/montserrat/montserrat-semibold.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-thin-italic.ttf b/assets/fonts/montserrat/montserrat-thin-italic.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-thin-italic.ttf rename to assets/fonts/montserrat/montserrat-thin-italic.ttf diff --git a/frontend/assets/fonts/montserrat/montserrat-thin.ttf b/assets/fonts/montserrat/montserrat-thin.ttf similarity index 100% rename from frontend/assets/fonts/montserrat/montserrat-thin.ttf rename to assets/fonts/montserrat/montserrat-thin.ttf diff --git a/assets/fonts/playfairdisplaysc/playfairdisplaysc-black-italic.ttf b/assets/fonts/playfairdisplaysc/playfairdisplaysc-black-italic.ttf new file mode 100644 index 0000000..e423fa0 Binary files /dev/null and b/assets/fonts/playfairdisplaysc/playfairdisplaysc-black-italic.ttf differ diff --git a/assets/fonts/playfairdisplaysc/playfairdisplaysc-black.ttf b/assets/fonts/playfairdisplaysc/playfairdisplaysc-black.ttf new file mode 100644 index 0000000..7d50fe3 Binary files /dev/null and b/assets/fonts/playfairdisplaysc/playfairdisplaysc-black.ttf differ diff --git a/assets/fonts/playfairdisplaysc/playfairdisplaysc-bold-italic.ttf b/assets/fonts/playfairdisplaysc/playfairdisplaysc-bold-italic.ttf new file mode 100644 index 0000000..036c404 Binary files /dev/null and b/assets/fonts/playfairdisplaysc/playfairdisplaysc-bold-italic.ttf differ diff --git a/assets/fonts/playfairdisplaysc/playfairdisplaysc-bold.ttf b/assets/fonts/playfairdisplaysc/playfairdisplaysc-bold.ttf new file mode 100644 index 0000000..d5a1aff Binary files /dev/null and b/assets/fonts/playfairdisplaysc/playfairdisplaysc-bold.ttf differ diff --git a/assets/fonts/playfairdisplaysc/playfairdisplaysc-regular-italic.ttf b/assets/fonts/playfairdisplaysc/playfairdisplaysc-regular-italic.ttf new file mode 100644 index 0000000..6220854 Binary files /dev/null and b/assets/fonts/playfairdisplaysc/playfairdisplaysc-regular-italic.ttf differ diff --git a/assets/fonts/playfairdisplaysc/playfairdisplaysc-regular.ttf b/assets/fonts/playfairdisplaysc/playfairdisplaysc-regular.ttf new file mode 100644 index 0000000..f3caa71 Binary files /dev/null and b/assets/fonts/playfairdisplaysc/playfairdisplaysc-regular.ttf differ diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-black-italic.ttf b/assets/fonts/sourcecodepro/sourcecodepro-black-italic.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-black-italic.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-black-italic.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-black.ttf b/assets/fonts/sourcecodepro/sourcecodepro-black.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-black.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-black.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-bold-italic.ttf b/assets/fonts/sourcecodepro/sourcecodepro-bold-italic.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-bold-italic.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-bold-italic.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-bold.ttf b/assets/fonts/sourcecodepro/sourcecodepro-bold.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-bold.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-bold.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-extrabold-italic.ttf b/assets/fonts/sourcecodepro/sourcecodepro-extrabold-italic.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-extrabold-italic.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-extrabold-italic.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-extrabold.ttf b/assets/fonts/sourcecodepro/sourcecodepro-extrabold.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-extrabold.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-extrabold.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-extralight-italic.ttf b/assets/fonts/sourcecodepro/sourcecodepro-extralight-italic.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-extralight-italic.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-extralight-italic.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-extralight.ttf b/assets/fonts/sourcecodepro/sourcecodepro-extralight.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-extralight.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-extralight.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-light-italic.ttf b/assets/fonts/sourcecodepro/sourcecodepro-light-italic.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-light-italic.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-light-italic.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-light.ttf b/assets/fonts/sourcecodepro/sourcecodepro-light.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-light.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-light.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-medium-italic.ttf b/assets/fonts/sourcecodepro/sourcecodepro-medium-italic.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-medium-italic.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-medium-italic.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-medium.ttf b/assets/fonts/sourcecodepro/sourcecodepro-medium.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-medium.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-medium.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-regular-italic.ttf b/assets/fonts/sourcecodepro/sourcecodepro-regular-italic.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-regular-italic.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-regular-italic.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-regular.ttf b/assets/fonts/sourcecodepro/sourcecodepro-regular.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-regular.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-regular.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-semibold-italic.ttf b/assets/fonts/sourcecodepro/sourcecodepro-semibold-italic.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-semibold-italic.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-semibold-italic.ttf diff --git a/frontend/assets/fonts/sourcecodepro/sourcecodepro-semibold.ttf b/assets/fonts/sourcecodepro/sourcecodepro-semibold.ttf similarity index 100% rename from frontend/assets/fonts/sourcecodepro/sourcecodepro-semibold.ttf rename to assets/fonts/sourcecodepro/sourcecodepro-semibold.ttf diff --git a/assets/main.js b/assets/main.js new file mode 100644 index 0000000..e21e131 --- /dev/null +++ b/assets/main.js @@ -0,0 +1,5 @@ +import './css/styles.css'; +//import 'htmx.org'; +import './ts/index.ts'; + +//window.htmx = require('htmx.org'); diff --git a/assets/ts/index.ts b/assets/ts/index.ts new file mode 100644 index 0000000..b833fd8 --- /dev/null +++ b/assets/ts/index.ts @@ -0,0 +1,10 @@ + +const navbarToggleButton = document.querySelector('#navbarToggleButton'); + +//button.addEventListener('click', () => { + //if (button.textContent === 'Click me') { + //button.textContent = 'Clicked'; + //} else { + //button.textContent = 'Click me'; + //} +//}); diff --git a/backend/config.py b/backend/config.py deleted file mode 100644 index 9ef0e3f..0000000 --- a/backend/config.py +++ /dev/null @@ -1,45 +0,0 @@ -"""This module provides global application settings. - -All settings are read from environment variables, but defaults are provided below -if the respective envvar is unset. -""" - -import os -from urllib.parse import quote_plus as url_encode -from functools import lru_cache - -from pydantic import BaseSettings, PostgresDsn - - -class Settings(BaseSettings): - """Contains application-specific configuration values. - - Reads the values from environment variables and falls back to default values - if the corresponding environment variable is unset. - """ - - app_name: str = os.getenv("APP_NAME", "MEDWingS") - app_version: str = os.getenv("APP_VERSION", "Unspecified") - - contact_name: str = os.getenv("CONTACT_NAME", "MEDWingS Development Team") - contact_email: str = os.getenv("CONTACT_EMAIL", "admin@example.com") - contact_url: str = os.getenv("CONTACT_URL", "https://www.example.com") - - # Debug mode has the following effects: - # - logs SQL operations - debug_mode: bool = False - if os.getenv("DEBUG_MODE", "false").lower() == "true": - debug_mode = True - - pg_hostname = os.getenv("POSTGRES_HOST", "db") - pg_port = os.getenv("POSTGRES_PORT", "5432") - pg_dbname = os.getenv("POSTGRES_DB", "medwings") - pg_user = url_encode(os.getenv("POSTGRES_USER", "medwings")) - pg_password = url_encode(os.getenv("POSTGRES_PASSWORD", "medwings")) - - -@lru_cache -def get_settings() -> Settings: - """Creates the settings once and returns a cached version on subsequent requests.""" - - return Settings() diff --git a/backend/crud/devices.py b/backend/crud/devices.py deleted file mode 100644 index a44160d..0000000 --- a/backend/crud/devices.py +++ /dev/null @@ -1,68 +0,0 @@ -"""This module handles CRUD operations for users in the database, based on pydanctic schemas.""" - -from datetime import datetime - -from sqlalchemy.orm import Session - -from backend.models import devices as devicemodel -from backend.models import users as usermodel -from backend.schemas import devices as deviceschema -from backend.exceptions import DataIntegrityException, NotFoundException - - -def create_device(db: Session, device: deviceschema.DeviceCreate) -> deviceschema.Device: - """Creates the specified device in the database.""" - - db_user = db.query(usermodel.User).filter(usermodel.User.id == device.owner_id).first() - if not db_user: - raise NotFoundException("Attempted to create a device for a nonexistent user.") - if not db_user.patient: - raise DataIntegrityException("Attempted to create a device for a user who is not a patient.") - - db_device = devicemodel.Device( - model_id=device.model_id, - owner_id=device.owner_id, - ) - db.add(db_device) - db.commit() - db.refresh(db_device) - - return deviceschema.Device.from_orm(db_device) - - -def read_device(db: Session, id: int) -> deviceschema.Device | None: - """Queries the db for a device with the specified id and returns it.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == id).first() - if not db_device: - raise NotFoundException(f"Device with id '{id}' was not found.") - - return deviceschema.Device.from_orm(db_device) - - -def update_device(db: Session, device: deviceschema.DeviceUpdate, id: int) -> deviceschema.Device: - """Updates the specified device's last seen time.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == id).first() - if not db_device: - raise NotFoundException(f"Device with id '{id}' was not found.") - - db_device.last_seen = device.last_seen - db.commit() - db.refresh(db_device) - - return deviceschema.Device.from_orm(db_device) - - -def delete_device(db: Session, id: int) -> deviceschema.Device: - """Deletes the user with the provided id from the db.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == id).first() - if not db_device: - raise NotFoundException(f"Device with id '{id}' was not found.") - device_copy = deviceschema.Device.from_orm(db_device) - - db.delete(db_device) - db.commit() - - return device_copy diff --git a/backend/crud/records.py b/backend/crud/records.py deleted file mode 100644 index 81c9281..0000000 --- a/backend/crud/records.py +++ /dev/null @@ -1,329 +0,0 @@ -"""This module handles CRUD operations for vitals records in the database, based on pydanctic schemas.""" - -from datetime import datetime - -from sqlalchemy.orm import Session - -from backend.models import records as recordmodel -from backend.models import devices as devicemodel -from backend.schemas import records as recordschema -from backend.exceptions import NotFoundException - - -def create_heart_rate_record(db: Session, record: recordschema.HeartRateRecordCreate, device_id: int) -> recordschema.HeartRateRecord: - """Creates the specified heart rate record in the database.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - db_record = recordmodel.HeartRateRecord( - value=record.value, - measured=record.measured, - device_id=device_id, - ) - db.add(db_record) - db.commit() - db.refresh(db_record) - - return recordschema.HeartRateRecord.from_orm(db_record) - - -def read_heart_rate_records_by_device( - db: Session, device_id: int, - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ) -> list[recordschema.HeartRateRecord]: - """Queries the db for a range of heart rate records captured by the device with the specified id.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - filters = [recordmodel.HeartRateRecord.device == db_device] - if since: - filters.append(recordmodel.HeartRateRecord.measured >= since) - if until: - filters.append(recordmodel.HeartRateRecord.measured <= until) - - return db.query(recordmodel.HeartRateRecord).filter(*filters).order_by(recordmodel.HeartRateRecord.measured.desc()).offset(skip).limit(limit).all() - - -def delete_heart_rate_record(db: Session, record_id: int) -> recordschema.HeartRateRecord: - """Deletes the heart rate record with the provided id from the db.""" - - db_record = db.query(recordmodel.HeartRateRecord).filter(recordmodel.HeartRateRecord.id == record_id).first() - if not db_record: - raise NotFoundException(f"Record with id '{record_id}' does not exist.") - record_copy = recordschema.HeartRateRecord.from_orm(db_record) - - db.delete(db_record) - db.commit() - - return record_copy - - -def create_avpu_score_record(db: Session, record: recordschema.AvpuScoreRecordCreate, device_id: int) -> recordschema.AvpuScoreRecord: - """Creates the specified AVPU score record in the database.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - db_record = recordmodel.AvpuScoreRecord( - value=record.value, - measured=record.measured, - device_id=device_id, - ) - db.add(db_record) - db.commit() - db.refresh(db_record) - - return recordschema.AvpuScoreRecord.from_orm(db_record) - - -def read_avpu_score_records_by_device( - db: Session, device_id: int, - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ) -> list[recordschema.AvpuScoreRecord]: - """Queries the db for a range of avpu score records captured by the device with the specified id.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - filters = [recordmodel.AvpuScoreRecord.device == db_device] - if since: - filters.append(recordmodel.AvpuScoreRecord.measured >= since) - if until: - filters.append(recordmodel.AvpuScoreRecord.measured <= until) - - return db.query(recordmodel.AvpuScoreRecord).filter(*filters).order_by(recordmodel.AvpuScoreRecord.measured.desc()).offset(skip).limit(limit).all() - - -def delete_avpu_score_record(db: Session, record_id: int) -> recordschema.AvpuScoreRecord: - """Deletes the avpu score record with the provided id from the db.""" - - db_record = db.query(recordmodel.AvpuScoreRecord).filter(recordmodel.AvpuScoreRecord.id == record_id).first() - if not db_record: - raise NotFoundException(f"Record with id '{record_id}' does not exist.") - record_copy = recordschema.AvpuScoreRecord.from_orm(db_record) - - db.delete(db_record) - db.commit() - - return record_copy - - -def create_body_temperature_record(db: Session, record: recordschema.BodyTemperatureRecordCreate, device_id: int) -> recordschema.BodyTemperatureRecord: - """Creates the specified body temperature record in the database.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - db_record = recordmodel.BodyTemperatureRecord( - value=record.value, - measured=record.measured, - device_id=device_id, - ) - db.add(db_record) - db.commit() - db.refresh(db_record) - - return recordschema.BodyTemperatureRecord.from_orm(db_record) - - -def read_body_temperature_records_by_device( - db: Session, device_id: int, - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ) -> list[recordschema.BodyTemperatureRecord]: - """Queries the db for a range of body temperature records captured by the device with the specified id.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - filters = [recordmodel.BodyTemperatureRecord.device == db_device] - if since: - filters.append(recordmodel.BodyTemperatureRecord.measured >= since) - if until: - filters.append(recordmodel.BodyTemperatureRecord.measured <= until) - - return db.query(recordmodel.BodyTemperatureRecord).filter(*filters).order_by(recordmodel.BodyTemperatureRecord.measured.desc()).offset(skip).limit(limit).all() - - -def delete_body_temperature_record(db: Session, record_id: int) -> recordschema.BodyTemperatureRecord: - """Deletes the body temperature record with the provided id from the db.""" - - db_record = db.query(recordmodel.BodyTemperatureRecord).filter(recordmodel.BodyTemperatureRecord.id == record_id).first() - if not db_record: - raise NotFoundException(f"Record with id '{record_id}' does not exist.") - record_copy = recordschema.BodyTemperatureRecord.from_orm(db_record) - - db.delete(db_record) - db.commit() - - return record_copy - - -def create_blood_pressure_record(db: Session, record: recordschema.BloodPressureRecordCreate, device_id: int) -> recordschema.BloodPressureRecord: - """Creates the specified blood pressure record in the database.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - db_record = recordmodel.BloodPressureRecord( - value_systolic=record.value_systolic, - value_diastolic=record.value_diastolic, - measured=record.measured, - device_id=device_id, - ) - db.add(db_record) - db.commit() - db.refresh(db_record) - - return recordschema.BloodPressureRecord.from_orm(db_record) - - -def read_blood_pressure_records_by_device( - db: Session, device_id: int, - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ) -> list[recordschema.BloodPressureRecord]: - """Queries the db for a range of blood pressure records captured by the device with the specified id.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - filters = [recordmodel.BloodPressureRecord.device == db_device] - if since: - filters.append(recordmodel.BloodPressureRecord.measured >= since) - if until: - filters.append(recordmodel.BloodPressureRecord.measured <= until) - - return db.query(recordmodel.BloodPressureRecord).filter(*filters).order_by(recordmodel.BloodPressureRecord.measured.desc()).offset(skip).limit(limit).all() - - -def delete_blood_pressure_record(db: Session, record_id: int) -> recordschema.BloodPressureRecord: - """Deletes the blood pressure record with the provided id from the db.""" - - db_record = db.query(recordmodel.BloodPressureRecord).filter(recordmodel.BloodPressureRecord.id == record_id).first() - if not db_record: - raise NotFoundException(f"Record with id '{record_id}' does not exist.") - record_copy = recordschema.BloodPressureRecord.from_orm(db_record) - - db.delete(db_record) - db.commit() - - return record_copy - - -def create_blood_oxygen_record(db: Session, record: recordschema.BloodOxygenRecordCreate, device_id: int) -> recordschema.BloodOxygenRecord: - """Creates the specified blood oxygen record in the database.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - db_record = recordmodel.BloodOxygenRecord( - value=record.value, - measured=record.measured, - device_id=device_id, - ) - db.add(db_record) - db.commit() - db.refresh(db_record) - - return recordschema.BloodOxygenRecord.from_orm(db_record) - - -def read_blood_oxygen_records_by_device( - db: Session, device_id: int, - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ) -> list[recordschema.BloodOxygenRecord]: - """Queries the db for a range of blood oxygen records captured by the device with the specified id.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - filters = [recordmodel.BloodOxygenRecord.device == db_device] - if since: - filters.append(recordmodel.BloodOxygenRecord.measured >= since) - if until: - filters.append(recordmodel.BloodOxygenRecord.measured <= until) - - return db.query(recordmodel.BloodOxygenRecord).filter(*filters).order_by(recordmodel.BloodOxygenRecord.measured.desc()).offset(skip).limit(limit).all() - - -def delete_blood_oxygen_record(db: Session, record_id: int) -> recordschema.BloodOxygenRecord: - """Deletes the blood oxygen record with the provided id from the db.""" - - db_record = db.query(recordmodel.BloodOxygenRecord).filter(recordmodel.BloodOxygenRecord.id == record_id).first() - if not db_record: - raise NotFoundException(f"Record with id '{record_id}' does not exist.") - record_copy = recordschema.BloodOxygenRecord.from_orm(db_record) - - db.delete(db_record) - db.commit() - - return record_copy - - -def create_respiration_score_record(db: Session, record: recordschema.RespirationScoreRecordCreate, device_id: int) -> recordschema.RespirationScoreRecord: - """Creates the specified respiration score record in the database.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - db_record = recordmodel.RespirationScoreRecord( - value=record.value, - measured=record.measured, - device_id=device_id, - ) - db.add(db_record) - db.commit() - db.refresh(db_record) - - return recordschema.RespirationScoreRecord.from_orm(db_record) - - -def read_respiration_score_records_by_device( - db: Session, device_id: int, - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ) -> list[recordschema.RespirationScoreRecord]: - """Queries the db for a range of respiration score records captured by the device with the specified id.""" - - db_device = db.query(devicemodel.Device).filter(devicemodel.Device.id == device_id).first() - if not db_device: - raise NotFoundException(f"Device with id '{device_id}' does not exist.") - - filters = [recordmodel.RespirationScoreRecord.device == db_device] - if since: - filters.append(recordmodel.RespirationScoreRecord.measured >= since) - if until: - filters.append(recordmodel.RespirationScoreRecord.measured <= until) - - return db.query(recordmodel.RespirationScoreRecord).filter(*filters).order_by(recordmodel.RespirationScoreRecord.measured.desc()).offset(skip).limit(limit).all() - - -def delete_respiration_score_record(db: Session, record_id: int) -> recordschema.RespirationScoreRecord: - """Deletes the respiration score record with the provided id from the db.""" - - db_record = db.query(recordmodel.RespirationScoreRecord).filter(recordmodel.RespirationScoreRecord.id == record_id).first() - if not db_record: - raise NotFoundException(f"Record with id '{record_id}' does not exist.") - record_copy = recordschema.RespirationScoreRecord.from_orm(db_record) - - db.delete(db_record) - db.commit() - - return record_copy diff --git a/backend/crud/users.py b/backend/crud/users.py deleted file mode 100644 index ce3806f..0000000 --- a/backend/crud/users.py +++ /dev/null @@ -1,144 +0,0 @@ -"""This module handles CRUD operations for users in the database, based on pydanctic schemas.""" - -from datetime import datetime - -from sqlalchemy.orm import Session - -from backend.models import users as usermodel -from backend.schemas import users as userschema -from backend.exceptions import NotFoundException - - -def hash_password(password: str) -> str: - """This is a placeholder for a secure password hashing algorithm. - - It will convert a plaintext password into a secure, salted hash, for storage - in the database. - """ - - # TODO actually hash the password! - return password - - -def _fill_missing_user_fields(db_user: usermodel.User) -> userschema.User: - """Fills all the fields of an instance of userschema.User that cannot be filled by pydantic. - - This function is necessary because the userschema is not a one-to-one reflection - of the database data model. I did not want the 'patient' and 'administrator' - database table to be encoded as their own top level JSON keys in serialized - user object. Instead, the user schema combines all fields from all user types. - This function fills the optional fields, depending on what type of user is - passed in. - """ - - - full_user = userschema.User.from_orm(db_user) - if db_user.patient: - full_user.devices = db_user.patient.devices - full_user.gender = db_user.patient.gender - full_user.date_of_birth = db_user.patient.date_of_birth - full_user.is_patient = True - full_user.is_admin = False - else: - full_user.is_patient = False - full_user.is_admin = True - - return full_user - - -def create_user(db: Session, user: userschema.UserCreate) -> userschema.User: - """Creates the specified user in the database.""" - - db_user = usermodel.User( - email=user.email, - first_name=user.first_name, - last_name=user.last_name, - password=hash_password(user.password), - ) - - if user.is_patient: - db_patient = usermodel.Patient( - user=db_user, - gender=user.gender, - date_of_birth=user.date_of_birth, - ) - db.add(db_patient) - else: - db_administrator = usermodel.Administrator( - user=db_user, - ) - db.add(db_administrator) - - db.commit() - - db.refresh(db_user) - return _fill_missing_user_fields(db_user) - - -def read_user(db: Session, id: int) -> userschema.User | None: - """Queries the db for a user with the specified id and returns them.""" - - db_user = db.query(usermodel.User).filter(usermodel.User.id == id).first() - if not db_user: - raise NotFoundException(f"User with id '{id}' not found.") - - return _fill_missing_user_fields(db_user) - - -def read_user_by_email(db: Session, email: str) -> userschema.User | None: - """Queries the db for a user with the specified email and returns them.""" - - db_user = db.query(usermodel.User).filter(usermodel.User.email == email).first() - if not db_user: - raise NotFoundException(f"User with email '{email}' not found.") - - return _fill_missing_user_fields(db_user) - - -def read_users(db: Session, skip: int = 0, limit: int = 0) -> list[userschema.User]: - """Returns an unfiltered range (by id) of users in the database.""" - - db_users = db.query(usermodel.User).offset(skip).limit(limit).all() - - full_users = [] - for db_user in db_users: - full_users.append(_fill_missing_user_fields(db_user)) - return full_users - - -def update_user(db: Session, user: userschema.UserUpdate, id: int) -> userschema.User: - """Updates the user with the provided id with all non-None fields from the input user.""" - - db_user = db.query(usermodel.User).filter(usermodel.User.id == id).first() - if not db_user: - raise NotFoundException(f"User with id '{id}' not found.") - - for key in ['gender', 'date_of_birth']: - value = getattr(user, key) - if value is not None: - setattr(db_user.patient, key, value) - for key in ['email', 'first_name', 'last_name']: - value = getattr(user, key) - if value is not None: - setattr(db_user, key, value) - if user.password is not None: - db_user.password = hash_password(user.password) - - db.commit() - db.refresh(db_user) - return _fill_missing_user_fields(db_user) - - -def delete_user(db: Session, id: int) -> userschema.User: - """Deletes the user with the provided id from the db.""" - - db_user = db.query(usermodel.User).filter(usermodel.User.id == id).first() - if not db_user: - raise NotFoundException(f"User with id '{id}' not found.") - user_copy = _fill_missing_user_fields(db_user) - - db.delete(db_user) - db.commit() - - user_copy.updated = datetime.now(user_copy.updated.tzinfo) - return user_copy diff --git a/backend/database/alembic.ini b/backend/database/alembic.ini deleted file mode 100644 index 778a46d..0000000 --- a/backend/database/alembic.ini +++ /dev/null @@ -1,110 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = migrations - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = ../.. - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python-dateutil library that can be -# installed by adding `alembic[tz]` to the pip requirements -# string value is passed to dateutil.tz.gettz() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to migrations/versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. -# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. -# Valid values for version_path_separator are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -version_path_separator = os # Use os.pathsep. Default configuration used for new projects. - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = driver://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/backend/database/engine.py b/backend/database/engine.py deleted file mode 100644 index 7b1aaa4..0000000 --- a/backend/database/engine.py +++ /dev/null @@ -1,27 +0,0 @@ -"""This module configures and provides the sqlalchemy session factory and base model.""" - -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.ext.declarative import declarative_base - -from backend.config import get_settings - - -s = get_settings() - -# The SQL driver is specified by the DSN-prefix below. -_pg_dsn = f"postgresql+psycopg2://{s.pg_user}:{s.pg_password}@{s.pg_hostname}:{s.pg_port}/{s.pg_dbname}" -engine = create_engine(_pg_dsn, echo=s.debug_mode) - -# SQLalchemy session factory -SessionLocal = sessionmaker(engine) -# SQLalchemy base model -Base = declarative_base() - - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() diff --git a/backend/database/migrations/env.py b/backend/database/migrations/env.py deleted file mode 100644 index fd9cb5c..0000000 --- a/backend/database/migrations/env.py +++ /dev/null @@ -1,80 +0,0 @@ -from logging.config import fileConfig - -from sqlalchemy import engine_from_config -from sqlalchemy import pool -from alembic import context - -from backend.database.engine import _pg_dsn as pg_connection_str - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config -config.set_main_option('sqlalchemy.url', pg_connection_str) - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -if config.config_file_name is not None: - fileConfig(config.config_file_name) - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = None - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def run_migrations_offline() -> None: - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online() -> None: - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - connectable = engine_from_config( - config.get_section(config.config_ini_section, {}), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) - - with connectable.connect() as connection: - context.configure( - connection=connection, target_metadata=target_metadata - ) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/backend/database/migrations/script.py.mako b/backend/database/migrations/script.py.mako deleted file mode 100644 index 55df286..0000000 --- a/backend/database/migrations/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade() -> None: - ${upgrades if upgrades else "pass"} - - -def downgrade() -> None: - ${downgrades if downgrades else "pass"} diff --git a/backend/database/migrations/versions/335e07a98bc8_create_user_tables.py b/backend/database/migrations/versions/335e07a98bc8_create_user_tables.py deleted file mode 100644 index 94946ea..0000000 --- a/backend/database/migrations/versions/335e07a98bc8_create_user_tables.py +++ /dev/null @@ -1,50 +0,0 @@ -"""create user tables - -Revision ID: 335e07a98bc8 -Revises: -Create Date: 2023-05-12 19:59:22.188464 - -""" -from alembic import op -from sqlalchemy import Column, ForeignKey, Integer, String, DateTime, Date, Enum -from sqlalchemy.sql.functions import now - -from backend.models.users import Gender - - -# revision identifiers, used by Alembic. -revision = '335e07a98bc8' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade() -> None: - op.create_table( - 'users', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('email', String, unique=True, nullable=False), - Column('password', String, nullable=False), - Column('created', DateTime(timezone=True), nullable=False, server_default=now()), - Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now()), - Column('first_name', String, nullable=False), - Column('last_name', String, nullable=False), - ) - - op.create_table( - 'administrators', - Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE"), primary_key=True,) - ) - - op.create_table( - 'patients', - Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE"), primary_key=True), - Column('date_of_birth', Date, nullable=False), - Column('gender', Enum(Gender), nullable=False), - ) - - -def downgrade() -> None: - op.drop_table('users') - op.drop_table('administrators') - op.drop_table('patients') diff --git a/backend/database/migrations/versions/7e5a8cabd3a4_create_device_tables.py b/backend/database/migrations/versions/7e5a8cabd3a4_create_device_tables.py deleted file mode 100644 index 249a891..0000000 --- a/backend/database/migrations/versions/7e5a8cabd3a4_create_device_tables.py +++ /dev/null @@ -1,61 +0,0 @@ -"""create device tables - -Revision ID: 7e5a8cabd3a4 -Revises: 335e07a98bc8 -Create Date: 2023-05-12 21:59:26.867894 - -""" -from alembic import op -from sqlalchemy import Column, ForeignKey, Integer, String, DateTime -from sqlalchemy.sql.functions import now - - -# revision identifiers, used by Alembic. -revision = '7e5a8cabd3a4' -down_revision = '335e07a98bc8' -branch_labels = None -depends_on = down_revision - - -def upgrade() -> None: - - device_models_table = op.create_table( - 'device_models', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('name', String, unique=True, nullable=False), - Column('picture_code', String, unique=True, nullable=False), - ) - - op.create_table( - 'devices', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('added', DateTime(timezone=True), nullable=False, server_default=now()), - Column('last_seen', DateTime(timezone=True), nullable=True, server_default=None), - Column('model_id', Integer, ForeignKey('device_models.id', ondelete="CASCADE"), nullable=False), - Column('owner_id', Integer, ForeignKey('patients.user_id', ondelete="CASCADE"), nullable=False), - ) - - # Fill table with known device models. - # Picture codes are generated using `python -c "import uuid; print(uuid.uuid4())"` - op.bulk_insert( - device_models_table, - [ - { - "name": "ScanWatch", - "picture_code": "133184d2-3b7c-4589-bc76-9736455a56b9", - }, - { - "name": "Thermo", - "picture_code": "3b0cd959-a293-488f-94d6-5f6dc0f775f3", - }, - { - "name": "BPM Core", - "picture_code": "36b60fc2-567f-4e7e-b76e-2a9006c0f0fb", - }, - ] - ) - - -def downgrade() -> None: - op.drop_table('devices') - op.drop_table('device_models') diff --git a/backend/database/migrations/versions/a7522972878d_create_records_tables.py b/backend/database/migrations/versions/a7522972878d_create_records_tables.py deleted file mode 100644 index 9fac45b..0000000 --- a/backend/database/migrations/versions/a7522972878d_create_records_tables.py +++ /dev/null @@ -1,79 +0,0 @@ -"""create records tables - -Revision ID: a7522972878d -Revises: 7e5a8cabd3a4 -Create Date: 2023-05-13 04:46:57.958926 - -""" -from alembic import op -from sqlalchemy import Column, ForeignKey, Integer, SmallInteger, DateTime, Enum, Numeric -from sqlalchemy.sql.functions import now - -from backend.models.records import AvpuScore, RespirationScore - - -# revision identifiers, used by Alembic. -revision = 'a7522972878d' -down_revision = '7e5a8cabd3a4' -branch_labels = None -depends_on = down_revision - - -def upgrade() -> None: - op.create_table( - 'heart_rate_records', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('measured', DateTime(timezone=True), nullable=False, index=True), - Column('value', SmallInteger, nullable=False), - Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True), - ) - - op.create_table( - 'avpu_score_records', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('measured', DateTime(timezone=True), nullable=False, index=True), - Column('value', Enum(AvpuScore), nullable=False), - Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True), - ) - - op.create_table( - 'body_temperature_records', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('measured', DateTime(timezone=True), nullable=False, index=True), - Column('value', Numeric(precision=4, scale=2, decimal_return_scale=2), nullable=False), - Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True), - ) - - op.create_table( - 'blood_pressure_records', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('measured', DateTime(timezone=True), nullable=False, index=True), - Column('value_systolic', SmallInteger, nullable=False), - Column('value_diastolic', SmallInteger, nullable=False), - Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True), - ) - - op.create_table( - 'blood_oxygen_records', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('measured', DateTime(timezone=True), nullable=False, index=True), - Column('value', Numeric(precision=5, scale=2, decimal_return_scale=2), nullable=False), - Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True), - ) - - op.create_table( - 'respiration_score_records', - Column('id', Integer, primary_key=True, autoincrement=True, index=True), - Column('measured', DateTime(timezone=True), nullable=False, index=True), - Column('value', Enum(RespirationScore), nullable=False), - Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True), - ) - - -def downgrade() -> None: - op.drop_table('heart_rate_records') - op.drop_table('avpu_score_records') - op.drop_table('body_temperature_records') - op.drop_table('blood_pressure_records') - op.drop_table('blood_oxygen_records') - op.drop_table('respiration_score_records') diff --git a/backend/exceptions.py b/backend/exceptions.py deleted file mode 100644 index 8e15048..0000000 --- a/backend/exceptions.py +++ /dev/null @@ -1,13 +0,0 @@ -"""This module is a collection of project-wide exceptions.""" - - -class NotFoundException(Exception): - """Raised when a resource was unexpectedly not found.""" - - pass - - -class DataIntegrityException(Exception): - """Raised when a resource was unexpectedly not found.""" - - pass diff --git a/backend/main.py b/backend/main.py deleted file mode 100644 index 4deeca6..0000000 --- a/backend/main.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Main entry point for the MEDWingS backend. - -This module defines the API routes provided by the backend. -""" - -from fastapi import FastAPI - -from backend import config -from backend.routes.devices import router as devices_router -from backend.routes.devices import tag_metadata as devices_tags -from backend.routes.users import router as users_router -from backend.routes.users import tag_metadata as users_tags -from backend.routes.records import router as records_router -from backend.routes.records import tag_metadata as records_tags - - -s = config.get_settings() -app = FastAPI( - title=f"{s.app_name} backend API", - description=f"This is the backend server API for {s.app_name}, a remote patient monitoring and early warning system.", - version=f"{s.app_version}", - contact={ - "name": f"{s.contact_name}", - "email": f"{s.contact_email}", - "url": f"{s.contact_url}", - }, - openapi_tags=[ - users_tags, - devices_tags, - records_tags, - ], -) - -app.include_router(devices_router) -app.include_router(users_router) -app.include_router(records_router) - - -@app.get("/hello/") -def hello(): - """Placeholder for a proper healthcheck endpoint.""" - - return "Hello World!" diff --git a/backend/models/devices.py b/backend/models/devices.py deleted file mode 100644 index 53f5756..0000000 --- a/backend/models/devices.py +++ /dev/null @@ -1,44 +0,0 @@ -"""This module defines the SQL data model for devices.""" - -import enum - -from sqlalchemy import Column, ForeignKey, Integer, String, DateTime, Uuid -from sqlalchemy.sql.functions import now -from sqlalchemy.orm import relationship - -from backend.database.engine import Base -from backend.models import records - - -class DeviceModel(Base): - """Model for the device_model table.""" - - __tablename__ = "device_models" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - name = Column('name', String, unique=True, nullable=False) - picture_code = Column('picture_code', String, unique=True, nullable=False) - - instances = relationship("Device", back_populates="model", uselist=True, cascade="all, delete") - - -class Device(Base): - """Model for the devices table.""" - - __tablename__ = "devices" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - added = Column('added', DateTime(timezone=True), nullable=False, server_default=now()) - last_seen = Column('last_seen', DateTime(timezone=True), nullable=True, server_default=None) - model_id = Column('model_id', Integer, ForeignKey('device_models.id', ondelete="CASCADE"), nullable=False) - owner_id = Column('owner_id', Integer, ForeignKey('patients.user_id', ondelete="CASCADE"), nullable=False) - - model = relationship(DeviceModel, back_populates="instances", uselist=False) - owner = relationship("Patient", back_populates="devices", uselist=False) - - heart_rate_records = relationship(records.HeartRateRecord, back_populates="device", uselist=True, cascade="all, delete") - avpu_score_records = relationship(records.AvpuScoreRecord, back_populates="device", uselist=True, cascade="all, delete") - blood_pressure_records = relationship(records.BloodPressureRecord, back_populates="device", uselist=True, cascade="all, delete") - blood_oxygen_records = relationship(records.BloodOxygenRecord, back_populates="device", uselist=True, cascade="all, delete") - body_temperature_records = relationship(records.BodyTemperatureRecord, back_populates="device", uselist=True, cascade="all, delete") - respiration_score_records = relationship(records.RespirationScoreRecord, back_populates="device", uselist=True, cascade="all, delete") diff --git a/backend/models/records.py b/backend/models/records.py deleted file mode 100644 index 521dde6..0000000 --- a/backend/models/records.py +++ /dev/null @@ -1,109 +0,0 @@ -"""This module defines the SQL data model for vital parameter records.""" - -import enum - -from sqlalchemy import Column, ForeignKey, DateTime, SmallInteger, Integer, Enum, Numeric -from sqlalchemy.orm import relationship - -from backend.database.engine import Base - - -class HeartRateRecord(Base): - """Model for the heart rate records table. Measured in beats per minute (bpm).""" - - __tablename__ = "heart_rate_records" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - measured = Column('measured', DateTime(timezone=True), nullable=False, index=True) - value = Column('value', SmallInteger, nullable=False) - device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True) - - device = relationship("Device", back_populates="heart_rate_records", uselist=False) - - -class AvpuScore(enum.Enum): - alert = 'a' - voice = 'v' - pain = 'p' - unresponsive = 'u' - - -class AvpuScoreRecord(Base): - """Model for the avpu score records table. Measured as a discrete classification.""" - - __tablename__ = "avpu_score_records" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - measured = Column('measured', DateTime(timezone=True), nullable=False, index=True) - value = Column('value', Enum(AvpuScore), nullable=False) - device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True) - - device = relationship("Device", back_populates="avpu_score_records", uselist=False) - - -class BodyTemperatureRecord(Base): - """Model for the body temperature records table. Measured in degrees Celsius. - - Two digits before and two digits after the decimal point: [-99.99, 99.99]. - """ - - __tablename__ = "body_temperature_records" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - measured = Column('measured', DateTime(timezone=True), nullable=False, index=True) - value = Column('value', Numeric(precision=4, scale=2, decimal_return_scale=2), nullable=False) - device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True) - - device = relationship("Device", back_populates="body_temperature_records", uselist=False) - - -class BloodPressureRecord(Base): - """Model for the blood pressure records table. Measured in (mmHg).""" - - __tablename__ = "blood_pressure_records" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - measured = Column('measured', DateTime(timezone=True), nullable=False, index=True) - value_systolic = Column('value_systolic', SmallInteger, nullable=False) - value_diastolic = Column('value_diastolic', SmallInteger, nullable=False) - device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True) - - device = relationship("Device", back_populates="blood_pressure_records", uselist=False) - - -class BloodOxygenRecord(Base): - """Model for the blood oxygen records table. Measured as a decimal percentage. - - Three digits before and two digits after the decimal point: [-999.99, 999.99]. - """ - - __tablename__ = "blood_oxygen_records" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - measured = Column('measured', DateTime(timezone=True), nullable=False, index=True) - value = Column('value', Numeric(precision=5, scale=2, decimal_return_scale=2), nullable=False) - device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True) - - device = relationship("Device", back_populates="blood_oxygen_records", uselist=False) - - -class RespirationScore(enum.Enum): - """Measures whether patient is experiencing shortness of breath, and its severity.""" - - none = 0 - low = 1 - medium = 2 - high = 3 - - -class RespirationScoreRecord(Base): - """Model for the respiration score records table. Measured as a discrete classification.""" - - __tablename__ = "respiration_score_records" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - measured = Column('measured', DateTime(timezone=True), nullable=False, index=True) - value = Column('value', Enum(RespirationScore), nullable=False) - device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True) - - device = relationship("Device", back_populates="respiration_score_records", uselist=False) diff --git a/backend/models/users.py b/backend/models/users.py deleted file mode 100644 index a3f3f19..0000000 --- a/backend/models/users.py +++ /dev/null @@ -1,59 +0,0 @@ -"""This module defines the SQL data model for users. - -All users are either Patients or Administrators. -""" - -import enum - -from sqlalchemy import Column, ForeignKey, Integer, String, DateTime, Date, Enum, CheckConstraint -from sqlalchemy.sql.functions import now -from sqlalchemy.orm import relationship - -from backend.database.engine import Base -from backend.models.devices import Device - - -class User(Base): - """Model for the users table. Contains user info common to all user classes.""" - - __tablename__ = "users" - - id = Column('id', Integer, primary_key=True, autoincrement=True, index=True) - email = Column('email', String, unique=True, nullable=False) - password = Column('password', String, nullable=False) - created = Column('created', DateTime(timezone=True), nullable=False, server_default=now()) - updated = Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now()) - first_name = Column('first_name', String, nullable=False) - last_name = Column('last_name', String, nullable=False) - - administrator = relationship("Administrator", back_populates="user", uselist=False, cascade="all, delete") - patient = relationship("Patient", back_populates="user", uselist=False, cascade="all, delete") - - -class Administrator(Base): - """Model for the administrators table. Contains user info specific to administrators.""" - - __tablename__ = "administrators" - - user_id = Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE"), primary_key=True) - user = relationship(User, back_populates="administrator", uselist=False, cascade="all, delete") - - -class Gender(enum.Enum): - """Gender (as assigned at birth) of a patient.""" - - male = 'm' - female = 'f' - - -class Patient(Base): - """Model for the patients table. Contains user info specific to patients.""" - - __tablename__ = "patients" - - user_id = Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE"), primary_key=True) - date_of_birth = Column('date_of_birth', Date, nullable=False) - gender = Column('gender', Enum(Gender), nullable=False) - - user = relationship(User, back_populates="patient", uselist=False, cascade="all, delete") - devices = relationship(Device, back_populates="owner", uselist=True, cascade="all, delete") diff --git a/backend/routes/devices.py b/backend/routes/devices.py deleted file mode 100644 index c1bb7be..0000000 --- a/backend/routes/devices.py +++ /dev/null @@ -1,52 +0,0 @@ -"""This module contains endpoints for operations related to devices.""" - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.orm import Session - -from backend.database.engine import get_db -from backend.schemas import devices as deviceschema -from backend.crud import devices as devicecrud -from backend.exceptions import NotFoundException - - -router = APIRouter( - prefix="/devices", - tags=["devices"] -) - -tag_metadata = { - "name": "devices", - "description": "Operations related to devices." -} - - -@router.post("/", response_model=deviceschema.Device) -def create_device(device: deviceschema.DeviceCreate, db: Session = Depends(get_db)): - try: - return devicecrud.create_device(db, device) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.get("/{id}", response_model=deviceschema.Device) -def read_device(id: int, db: Session = Depends(get_db)): - try: - return devicecrud.read_device(db, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.patch("/{id}", response_model=deviceschema.Device) -def update_device(id: int, device: deviceschema.DeviceUpdate, db: Session = Depends(get_db)): - try: - return devicecrud.update_device(db, device, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.delete("/{id}", response_model=deviceschema.Device) -def delete_device(id: int, db: Session = Depends(get_db)): - try: - return devicecrud.delete_device(db, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) diff --git a/backend/routes/records.py b/backend/routes/records.py deleted file mode 100644 index 389c640..0000000 --- a/backend/routes/records.py +++ /dev/null @@ -1,191 +0,0 @@ -"""This module contains endpoints for operations related to vitals records.""" - -from datetime import datetime -from datetime import datetime - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.orm import Session - -from backend.database.engine import get_db -from backend.schemas import records as recordschema -from backend.crud import records as recordcrud -from backend.exceptions import NotFoundException - - -router = APIRouter( - prefix="/devices", - tags=["records"] -) - -tag_metadata = { - "name": "records", - "description": "Operations related to vitals records." -} - - -@router.post("/{device_id}/heart-rate/", response_model=recordschema.HeartRateRecord) -def create_heart_rate_record(record: recordschema.HeartRateRecordCreate, device_id: int, db: Session = Depends(get_db)): - try: - return recordcrud.create_heart_rate_record(db, record, device_id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.get("/{device_id}/heart-rate/", response_model=list[recordschema.HeartRateRecord]) -def read_heart_rate_records( - device_id: int, db: Session = Depends(get_db), - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ): - try: - return recordcrud.read_heart_rate_records_by_device(db, device_id, since, until, skip, limit) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.delete("/{device_id}/heart-rate/{record_id}", response_model=recordschema.HeartRateRecord) -def delete_heart_rate_record(id: int, db: Session = Depends(get_db)): - try: - return recordcrud.delete_heart_rate_record(db, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.post("/{device_id}/avpu-score/", response_model=recordschema.AvpuScoreRecord) -def create_avpu_score_record(record: recordschema.AvpuScoreRecordCreate, device_id: int, db: Session = Depends(get_db)): - try: - return recordcrud.create_avpu_score_record(db, record, device_id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.get("/{device_id}/avpu-score/", response_model=list[recordschema.AvpuScoreRecord]) -def read_avpu_score_records( - device_id: int, db: Session = Depends(get_db), - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ): - try: - return recordcrud.read_avpu_score_records_by_device(db, device_id, since, until, skip, limit) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.delete("/{device_id}/avpu-score/{record_id}", response_model=recordschema.AvpuScoreRecord) -def delete_avpu_score_record(id: int, db: Session = Depends(get_db)): - try: - return recordcrud.delete_avpu_score_record(db, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.post("/{device_id}/body-temperature/", response_model=recordschema.BodyTemperatureRecord) -def create_body_temperature_record(record: recordschema.BodyTemperatureRecordCreate, device_id: int, db: Session = Depends(get_db)): - try: - return recordcrud.create_body_temperature_record(db, record, device_id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.get("/{device_id}/body-temperature/", response_model=list[recordschema.BodyTemperatureRecord]) -def read_body_temperature_records( - device_id: int, db: Session = Depends(get_db), - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ): - try: - return recordcrud.read_body_temperature_records_by_device(db, device_id, since, until, skip, limit) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.delete("/{device_id}/body-temperature/{record_id}", response_model=recordschema.BodyTemperatureRecord) -def delete_body_temperature_record(id: int, db: Session = Depends(get_db)): - try: - return recordcrud.delete_body_temperature_record(db, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.post("/{device_id}/blood-pressure/", response_model=recordschema.BloodPressureRecord) -def create_blood_pressure_record(record: recordschema.BloodPressureRecordCreate, device_id: int, db: Session = Depends(get_db)): - try: - return recordcrud.create_blood_pressure_record(db, record, device_id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.get("/{device_id}/blood-pressure/", response_model=list[recordschema.BloodPressureRecord]) -def read_blood_pressure_records( - device_id: int, db: Session = Depends(get_db), - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ): - try: - return recordcrud.read_blood_pressure_records_by_device(db, device_id, since, until, skip, limit) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.delete("/{device_id}/blood-pressure/{record_id}", response_model=recordschema.BloodPressureRecord) -def delete_blood_pressure_record(id: int, db: Session = Depends(get_db)): - try: - return recordcrud.delete_blood_pressure_record(db, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.post("/{device_id}/blood-oxygen/", response_model=recordschema.BloodOxygenRecord) -def create_blood_oxygen_record(record: recordschema.BloodOxygenRecordCreate, device_id: int, db: Session = Depends(get_db)): - try: - return recordcrud.create_blood_oxygen_record(db, record, device_id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.get("/{device_id}/blood-oxygen/", response_model=list[recordschema.BloodOxygenRecord]) -def read_blood_oxygen_records( - device_id: int, db: Session = Depends(get_db), - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ): - try: - return recordcrud.read_blood_oxygen_records_by_device(db, device_id, since, until, skip, limit) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.delete("/{device_id}/blood-oxygen/{record_id}", response_model=recordschema.BloodOxygenRecord) -def delete_blood_oxygen_record(id: int, db: Session = Depends(get_db)): - try: - return recordcrud.delete_blood_oxygen_record(db, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.post("/{device_id}/respiration-score/", response_model=recordschema.RespirationScoreRecord) -def create_respiration_score_record(record: recordschema.RespirationScoreRecordCreate, device_id: int, db: Session = Depends(get_db)): - try: - return recordcrud.create_respiration_score_record(db, record, device_id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.get("/{device_id}/respiration-score/", response_model=list[recordschema.RespirationScoreRecord]) -def read_respiration_score_records( - device_id: int, db: Session = Depends(get_db), - since: datetime | None = None, until: datetime | None = None, - skip: int = 0, limit: int = 100, - ): - try: - return recordcrud.read_respiration_score_records_by_device(db, device_id, since, until, skip, limit) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.delete("/{device_id}/respiration-score/{record_id}", response_model=recordschema.RespirationScoreRecord) -def delete_respiration_score_record(id: int, db: Session = Depends(get_db)): - try: - return recordcrud.delete_respiration_score_record(db, id) - except NotFoundException as e: - raise HTTPException(404, str(e)) diff --git a/backend/routes/users.py b/backend/routes/users.py deleted file mode 100644 index 78e0cfb..0000000 --- a/backend/routes/users.py +++ /dev/null @@ -1,60 +0,0 @@ -"""This module contains endpoints for operations related to users.""" - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.orm import Session - -from backend.database.engine import get_db -from backend.schemas import users as userschema -from backend.crud import users as usercrud -from backend.exceptions import NotFoundException - - -router = APIRouter( - prefix="/users", - tags=["users"] -) - -tag_metadata = { - "name": "users", - "description": "Operations related to users." -} - - -@router.post("/", response_model=userschema.User) -def create_user(user: userschema.UserCreate, db: Session = Depends(get_db)): - try: - # An exception is expected, because we need to check if a user with this email is already registered - usercrud.read_user_by_email(db, email=user.email) - raise HTTPException(400, "A user with this email address is already registered.") - except NotFoundException: - return usercrud.create_user(db=db, user=user) - - -@router.get("/{id}", response_model=userschema.User) -def read_user(id: int, db: Session = Depends(get_db)): - try: - return usercrud.read_user(db=db, id=id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.get("/", response_model=list[userschema.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = usercrud.read_users(db=db, skip=skip, limit=limit) - return users - - -@router.patch("/{id}", response_model=userschema.User) -def update_user(id: int, user: userschema.UserUpdate, db: Session = Depends(get_db)): - try: - return usercrud.update_user(db=db, user=user, id=id) - except NotFoundException as e: - raise HTTPException(404, str(e)) - - -@router.delete("/{id}", response_model=userschema.User) -def delete_user(id: int, db: Session = Depends(get_db)): - try: - return usercrud.delete_user(db=db, id=id) - except NotFoundException as e: - raise HTTPException(404, str(e)) diff --git a/backend/schemas/devices.py b/backend/schemas/devices.py deleted file mode 100644 index 600de7d..0000000 --- a/backend/schemas/devices.py +++ /dev/null @@ -1,71 +0,0 @@ -"""This module declareds the pydantic schema representation for devices.""" - -from datetime import datetime -from abc import ABC - -from pydantic import BaseModel, validator - - -class DeviceModel(BaseModel): - id: int - name: str - picture_code: str - - @validator('name') - def assert_name_is_valid(cls, name): - if not len(name): - raise ValueError("Name must not be empty.") - return name - - @validator('picture_code') - def assert_picture_is_valid(cls, picture): - if not len(picture): - raise ValueError("Picture must not be empty.") - return picture - - class Config: - orm_mode = True - - -class DeviceCreate(BaseModel): - """Device schema used for Device creation.""" - - owner_id: int - model_id: int - - @validator('model_id') - def assert_model_id_is_valid(cls, model_id): - if not 1 <= model_id <= 3: - raise ValueError("Model id is invalid.") - return model_id - - -class DeviceUpdate(BaseModel): - """Device schema used for Device updates.""" - - last_seen: datetime - - -class Device(BaseModel): - """Device schema used for Device display.""" - - id: int - model: DeviceModel - added: datetime - last_seen: datetime | None - - @validator('added') - def assert_added_is_valid(cls, added): - if added >= datetime.now(added.tzinfo): - raise ValueError("Date added cannot be in the future.") - return added - - @validator('last_seen') - def assert_last_seen_is_valid(cls, last_seen): - if last_seen: - if last_seen >= datetime.now(last_seen.tzinfo): - raise ValueError("Date last seen cannot be in the future.") - return last_seen - - class Config: - orm_mode = True diff --git a/backend/schemas/records.py b/backend/schemas/records.py deleted file mode 100644 index 7b92be3..0000000 --- a/backend/schemas/records.py +++ /dev/null @@ -1,142 +0,0 @@ -"""This module declareds the pydantic schema representation for devices.""" - -from datetime import datetime -from abc import ABC -from decimal import Decimal - -from pydantic import BaseModel, validator - -from backend.models.records import AvpuScore, RespirationScore - - -class AbstractRecordCreate(BaseModel, ABC): - """Base class containing fields common to all vitals records during creation.""" - - measured: datetime - - @validator('measured') - def assert_measured_is_valid(cls, measured: datetime) -> datetime: - if measured >= datetime.now(tz=measured.tzinfo): - raise ValueError("Time of measurement cannot be in the future.") - return measured - -class AbstractRecord(BaseModel, ABC): - """Base class containing fields common to all vitals records for display.""" - - id: int - measured: datetime - device_id: int - - @validator('measured') - def assert_measured_is_valid(cls, measured: datetime) -> datetime: - if measured >= datetime.now(tz=measured.tzinfo): - raise ValueError("Time of measurement cannot be in the future.") - return measured - - class Config: - orm_mode = True - - -class AbstractHeartRateRecord(BaseModel, ABC): - value: int - - @validator('value') - def assert_value_is_valid(cls, value): - if value < 0: - raise ValueError("Value cannot be negative.") - if value >= 32767: - raise ValueError("Value is too large.") - return value - -class HeartRateRecordCreate(AbstractRecordCreate, AbstractHeartRateRecord): - pass - -class HeartRateRecord(AbstractRecord, AbstractHeartRateRecord): - pass - - -class AbstractAvpuScoreRecord(BaseModel, ABC): - value: AvpuScore - -class AvpuScoreRecordCreate(AbstractRecordCreate, AbstractAvpuScoreRecord): - pass - -class AvpuScoreRecord(AbstractRecord, AbstractAvpuScoreRecord): - pass - - -class AbstractBodyTemperatureRecord(BaseModel, ABC): - value: Decimal - - @validator('value') - def assert_value_is_valid(cls, value: Decimal) -> Decimal: - if value < 0: - raise ValueError("Value cannot be negative.") - if value >= 100: - raise ValueError("Value cannot exceed '99.99'.") - if len(value.as_tuple().digits) > 4: - raise ValueError("Value can have at most two digits after the decimal point.") - return value - -class BodyTemperatureRecordCreate(AbstractRecordCreate, AbstractBodyTemperatureRecord): - pass - -class BodyTemperatureRecord(AbstractRecord, AbstractBodyTemperatureRecord): - pass - - -class AbstractBloodPressureRecord(BaseModel, ABC): - value_systolic: int - value_diastolic: int - - @validator('value_systolic') - def assert_value_systolic_is_valid(cls, value_systolic): - if value_systolic < 0: - raise ValueError("Value (systolic) cannot be negative.") - if value_systolic >= 32767: - raise ValueError("Value (systolic) is too large.") - return value_systolic - - @validator('value_diastolic') - def assert_value_diastolic_is_valid(cls, value_diastolic): - if value_diastolic < 0: - raise ValueError("Value (diastolic) cannot be negative.") - if value_diastolic >= 32767: - raise ValueError("Value (diastolic) is too large.") - return value_diastolic - -class BloodPressureRecordCreate(AbstractRecordCreate, AbstractBloodPressureRecord): - pass - -class BloodPressureRecord(AbstractRecord, AbstractBloodPressureRecord): - pass - - -class AbstractBloodOxygenRecord(BaseModel, ABC): - value: Decimal - - @validator('value') - def assert_value_is_valid(cls, value: Decimal) -> Decimal: - if value < 0: - raise ValueError("Value cannot be negative.") - if value > 100: - raise ValueError("Value cannot exceed '100.00'.") - if len(value.as_tuple().digits) > 5: - raise ValueError("Value can have at most two digits after the decimal point.") - return value - -class BloodOxygenRecordCreate(AbstractRecordCreate, AbstractBloodOxygenRecord): - pass - -class BloodOxygenRecord(AbstractRecord, AbstractBloodOxygenRecord): - pass - - -class AbstractRespirationScoreScoreRecord(BaseModel, ABC): - value: RespirationScore - -class RespirationScoreRecordCreate(AbstractRecordCreate, AbstractRespirationScoreScoreRecord): - pass - -class RespirationScoreRecord(AbstractRecord, AbstractRespirationScoreScoreRecord): - pass diff --git a/backend/schemas/users.py b/backend/schemas/users.py deleted file mode 100644 index d557ec4..0000000 --- a/backend/schemas/users.py +++ /dev/null @@ -1,148 +0,0 @@ -"""This module declareds the pydantic schema representation for users. - -Note that it is not a direct representation of how users are modeled in the -database. Instead, the User schema class contains all attributes from all user classes -as optional attributes. - -I haven't figured out a smart way to do this with pydantic yet, so behold the -inheritance hellhole below. -""" - -from datetime import datetime, date -from abc import ABC -from typing import Optional - -from pydantic import BaseModel, validator - -from backend.models.users import Gender -from backend.schemas.devices import Device - - -class AbstractUserInfoValidation(BaseModel, ABC): - """Base class providing common field validators.""" - - @validator('email', check_fields=False) - def assert_email_is_valid(cls, email): - if email is not None: - if not len(email): - raise ValueError("Email must not be empty.") - # TODO implement more robust check - return email - - @validator('first_name', check_fields=False) - def assert_first_name_is_valid(cls, first_name): - if first_name is not None: - if not len(first_name): - raise ValueError("First Name must not be empty.") - return first_name - - @validator('last_name', check_fields=False) - def assert_last_name_is_valid(cls, last_name): - if last_name is not None: - if not len(last_name): - raise ValueError("Last Name must not be empty.") - return last_name - - @validator('date_of_birth', check_fields=False) - def assert_dob_is_valid(cls, dob): - if dob is not None: - if dob >= date.today(): - raise ValueError("Date of birth cannot be in the future.") - return dob - - -class AbstractUser(AbstractUserInfoValidation, ABC): - """Base class for attributes common to user creation and user representation. - - A user must be either a patient or an administrator. If a user is a patient, - they must specify valid 'date_of_birth' and 'gender' attributes. - """ - - email: str - first_name: str - last_name: str - - gender: Optional[Gender] - date_of_birth: Optional[date] - is_patient: Optional[bool] - - is_admin: Optional[bool] - - @validator('is_admin') - def assert_tegridy(cls, is_admin, values): - """Ensures logical model integrity when optional fields are set.""" - - if values['is_patient']: - if is_admin: - raise ValueError('User cannot be both patient and admin.') - for key in ['gender', 'date_of_birth']: - if key not in values or values[key] is None: - raise ValueError(f"Must specify key '{key}' for patients.") - if not values['is_patient'] and not is_admin: - raise ValueError(f'User must either be patient or admin.') - return is_admin - - -class UserCreate(AbstractUser): - """Scheme for user creation.""" - - password: str - password_confirmation: str - - @validator('password_confirmation') - def assert_passwords_match(cls, password_confirmation, values): - if not password_confirmation == values['password']: - raise ValueError("Passwords do not match.") - if len(password_confirmation) < 1: - # TODO use more robust password rules - raise ValueError("Password must not be empty.") - return password_confirmation - - -class UserUpdate(AbstractUserInfoValidation): - """Scheme for user updates. - - All fields here are optional, but passwords must match if at least one was - provided. - Note that even administrator updates can specify 'gender' and 'date_of_birth' - fields, the function inserting the update into the db should handle this (and - just ignore the fields). - Switching user types is prohibited. - """ - - email: Optional[str] - first_name: Optional[str] - last_name: Optional[str] - - gender: Optional[Gender] - date_of_birth: Optional[date] - - password: Optional[str] = None - password_confirmation: Optional[str] = None - - @validator('password_confirmation') - def assert_passwords_match_or_are_both_none(cls, password_confirmation, values): - password = values.get('password') - if None not in [password, password_confirmation]: - if not password == password_confirmation: - raise ValueError("Passwords do not match.") - if len(password_confirmation) < 1: - # TODO use more robust password rules - raise ValueError("Password must not be empty.") - return password_confirmation - - -class User(AbstractUser): - """Final representation of all types of users, wrapped into one User schema. - - The id, created and updated fields are filled by the db during creation, so - they are not needed in the parent classes. - """ - - id: int - created: datetime - updated: datetime - devices: list[Device] = [] - - class Config: - orm_mode = True diff --git a/development.Caddyfile b/development.Caddyfile deleted file mode 100644 index 7baee06..0000000 --- a/development.Caddyfile +++ /dev/null @@ -1,38 +0,0 @@ -:3000 { - - encode zstd gzip - - @staticfiles { - method GET - path /static/* - } - handle @staticfiles { - file_server { - root /app/public/ - } - } - - handle_path /api/* { - reverse_proxy * backend:3001 - } - - @robots { - method GET - path /robots.txt - path /sitemap.xml - } - handle @robots { - file_server { - root /app/robots/ - } - } - - handle * { - reverse_proxy * parcel:1234 - } - - log { - output stderr - format console - } -} diff --git a/development.backend.Dockerfile b/development.backend.Dockerfile deleted file mode 100644 index 802c5c4..0000000 --- a/development.backend.Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM python:3 - -# Create non-root user -ARG CUSTOM_UID -ARG CUSTOM_GID -ENV CUSTOM_USERNAME=backend -ENV CUSTOM_GROUPNAME=backend -RUN groupadd --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \ - useradd --uid ${CUSTOM_UID:-1000} --gid ${CUSTOM_GID:-1000} --create-home --shell /bin/bash ${CUSTOM_USERNAME} && \ - mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app - -# Copy source files -WORKDIR /app -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} requirements.txt /app/ -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} backend /app/backend/ - -# Install dependencies -RUN pip install -r requirements.txt - -# Run ASGI server -EXPOSE 3001/tcp -USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} -ENTRYPOINT ["uvicorn", "backend.main:app", "--root-path", "/api", "--host", "0.0.0.0", "--port", "3001", "--access-log", "--use-colors", "--log-level", "debug", "--reload"] diff --git a/development.caddy.Dockerfile b/development.caddy.Dockerfile new file mode 100644 index 0000000..d1d307f --- /dev/null +++ b/development.caddy.Dockerfile @@ -0,0 +1,24 @@ +# syntax=docker/dockerfile:1 + +FROM alpine:latest + +# Install caddy +RUN apk add --no-cache caddy + +# Create non-root user +ARG CUSTOM_UID +ARG CUSTOM_GID +ENV CUSTOM_USERNAME=webserver +ENV CUSTOM_GROUPNAME=webserver +RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \ + adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \ + mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app + +# Copy caddy config +WORKDIR /app +COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} Caddyfile /app/ + +# Run Caddy in development mode +USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} +EXPOSE 8000 +ENTRYPOINT ["caddy", "run", "--config", "/app/Caddyfile", "--adapter", "caddyfile", "--watch"] diff --git a/development.django.Dockerfile b/development.django.Dockerfile new file mode 100644 index 0000000..31ebed1 --- /dev/null +++ b/development.django.Dockerfile @@ -0,0 +1,33 @@ +# syntax=docker/dockerfile:1 + +FROM python:alpine + +# Install cron daemon and supervisord +RUN apk add --no-cache dcron supervisor + +# Create non-root user +ARG CUSTOM_UID +ARG CUSTOM_GID +ENV CUSTOM_USERNAME=django +ENV CUSTOM_GROUPNAME=django +RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \ + adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \ + mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app +ENV PATH "$PATH:/home/${CUSTOM_GROUPNAME}/.local/bin" + +# Add supervisord conf +COPY development.supervisord.conf /etc/supervisord.conf + +# Add cron job +COPY --chmod=600 django.crontab /etc/crontabs/django + +# Copy source files +WORKDIR /app +COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} app/ /app/ + +# Install dependencies +RUN pip install -r requirements.txt + +# Run supervisord +EXPOSE 8000/tcp +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] diff --git a/development.docker-compose.yml b/development.docker-compose.yml index dfaa1b7..d3642f0 100644 --- a/development.docker-compose.yml +++ b/development.docker-compose.yml @@ -3,88 +3,96 @@ version: "3" services: - frontend: - container_name: frontend + medwings-caddy: + container_name: medwings-caddy restart: unless-stopped - depends_on: - - parcel build: context: . - dockerfile: ./development.frontend.Dockerfile + dockerfile: development.caddy.Dockerfile args: CUSTOM_UID: 1000 CUSTOM_GID: 1000 - environment: - TZ: Europe/Berlin ports: - - "8000:3000" + - "8000:8000" volumes: - - ./public/:/app/public/:ro - - ./robots/:/app/robots/:ro - - ./development.Caddyfile:/app/Caddyfile:ro - parcel: - container_name: parcel - restart: unless-stopped - build: - context: . - dockerfile: ./development.parcel.Dockerfile - args: - CUSTOM_UID: 1000 - CUSTOM_GID: 1000 - volumes: - - ./frontend/:/app/frontend/:ro - - ./postcss.config.js:/app/postcss.config.js:ro - - ./.postcssrc:/app/.postcssrc:ro - - ./tailwind.config.js:/app/tailwind.config.js:ro - expose: - - "1234" - backend: - container_name: backend + - ./Caddyfile:/app/Caddyfile + environment: + TZ: ${TIMEZONE} + medwings-django: + container_name: medwings-django restart: unless-stopped depends_on: - - frontend - - db + - medwings-caddy + - ${PG_HOST} build: context: . - dockerfile: ./development.backend.Dockerfile + dockerfile: ./development.django.Dockerfile args: CUSTOM_UID: 1000 CUSTOM_GID: 1000 expose: - - "3001" + - "8000" volumes: - - ./backend/:/app/backend/:ro - - ./requirements.txt:/app/requirements.txt:ro + - ./app/authentication/:/app/authentication:ro + - ./app/core/:/app/core:ro + - ./app/gotify/:/app/gotify:ro + - ./app/manage.py:/app/manage.py:ro + - ./app/medwings/:/app/medwings:ro + - ./app/requirements.txt:/app/requirements.txt:ro + - ./app/static/:/app/static:ro + - ./app/withings/:/app/withings:ro environment: - APP_NAME: "MEDWingS" - ADMIN_EMAIL: "admin@example.com" - DEBUG_MODE: "true" - POSTGRES_HOST: "db" - POSTGRES_PORT: "5432" - POSTGRES_DB: "medwings" - POSTGRES_USER: "medwings" - POSTGRES_PASSWORD: "medwings" - db: - image: postgres - container_name: db + TZ: ${TIMEZONE} + PG_NAME: ${PG_NAME} + PG_USER: ${PG_USER} + PG_PASSWORD: ${PG_PASSWORD} + PG_HOST: ${PG_HOST} + PG_PORT: ${PG_PORT} + WITHINGS_CLIENT_ID: ${WITHINGS_CLIENT_ID} + WITHINGS_CLIENT_SECRET: ${WITHINGS_CLIENT_SECRET} + GOTIFY_USER: ${GOTIFY_USER} + GOTIFY_PASSWORD: ${GOTIFY_PASSWORD} + GOTIFY_HOST: ${GOTIFY_HOST} + GOTIFY_PUBLIC_URL: ${GOTIFY_PUBLIC_URL} + medwings-postgres: + image: postgres:alpine + container_name: ${PG_HOST} restart: unless-stopped expose: - - "5432" + - ${PG_PORT} volumes: - ./.postgres:/var/lib/postgresql/data environment: - POSTGRES_DB: "medwings" - POSTGRES_USER: "medwings" - POSTGRES_PASSWORD: "medwings" - pgweb: + POSTGRES_DB: ${PG_NAME} + POSTGRES_USER: ${PG_USER} + POSTGRES_PASSWORD: ${PG_PASSWORD} + TZ: ${TIMEZONE} + medwings-gotify: + image: gotify/server + container_name: medwings-gotify + restart: unless-stopped + ports: + - "8001:80" + volumes: + - ./.gotify:/app/data + environment: + TZ: ${TIMEZONE} + GOTIFY_SERVER_SSL_REDIRECTTOHTTPS: false + GOTIFY_DEFAULTUSER_NAME: ${GOTIFY_USER} + GOTIFY_DEFAULTUSER_PASS: ${GOTIFY_PASSWORD} + GOTIFY_SERVER_CORS_ALLOWORIGINS: "- \"localhost:8000\"\n- \"medwings.lobbes.dev\"" + GOTIFY_SERVER_CORS_ALLOWMETHODS: "- \"GET\"\n- \"POST\"" + GOTIFY_SERVER_CORS_ALLOWHEADERS: "- \"Authorization\"\n- \"content-type\"" + medwings-pgweb: image: sosedoff/pgweb - container_name: pgweb + container_name: medwings-pgweb restart: unless-stopped depends_on: - - db + - ${PG_HOST} ports: - - "8001:8081" + - "8002:8081" environment: - DATABASE_URL: "postgres://medwings:medwings@db:5432/medwings?sslmode=disable" + PGWEB_DATABASE_URL: postgres://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/${PG_NAME}?sslmode=disable + TZ: ${TIMEZONE} ... diff --git a/development.frontend.Dockerfile b/development.frontend.Dockerfile deleted file mode 100644 index 05e2ccd..0000000 --- a/development.frontend.Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM debian:latest - -# Install Caddy -ENV DEBIAN_FRONTEND=noninteractive -RUN apt update && apt install -y curl && \ - apt install -y debian-keyring debian-archive-keyring apt-transport-https && \ - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && \ - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list && \ - apt update && apt install -y caddy && \ - rm -rf /var/lib/apt/lists/* - -# Create non-root user -ARG CUSTOM_UID -ARG CUSTOM_GID -ENV CUSTOM_USERNAME=webserver -ENV CUSTOM_GROUPNAME=webserver -RUN groupadd --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \ - useradd --uid ${CUSTOM_UID:-1000} --gid ${CUSTOM_GID:-1000} --create-home --shell /bin/bash ${CUSTOM_USERNAME} && \ - mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app - -# Copy source files -WORKDIR /app -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} development.Caddyfile /app/ -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} public /app/public/ -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} robots /app/robots/ - -# Install dependencies -USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} - -# Run Caddy in development mode -ENTRYPOINT ["caddy", "run", "--config", "/app/development.Caddyfile", "--adapter", "caddyfile", "--watch"] diff --git a/development.parcel.Dockerfile b/development.parcel.Dockerfile deleted file mode 100644 index f5ddba1..0000000 --- a/development.parcel.Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM debian:latest - -# Install packages -ENV DEBIAN_FRONTEND=noninteractive -RUN apt update && apt install -y curl && \ - curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && apt-get install -y nodejs && \ - apt install -y debian-keyring debian-archive-keyring apt-transport-https && \ - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && \ - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list && \ - apt update && apt install -y caddy && \ - rm -rf /var/lib/apt/lists/* - -# Create non-root user -ARG CUSTOM_UID -ARG CUSTOM_GID -ENV CUSTOM_USERNAME=webserver -ENV CUSTOM_GROUPNAME=webserver -RUN groupadd --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \ - useradd --uid ${CUSTOM_UID:-1000} --gid ${CUSTOM_GID:-1000} --create-home --shell /bin/bash ${CUSTOM_USERNAME} && \ - mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app - -# Copy source files -WORKDIR /app -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} package.json package-lock.json .postcssrc postcss.config.js tailwind.config.js /app/ -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} frontend /app/frontend/ - -# Install dependencies -USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} -RUN npm install - -# Run the parcel file watcher -ENTRYPOINT ["npx", "parcel", "frontend/src/html/index.html"] diff --git a/development.supervisord.conf b/development.supervisord.conf new file mode 100644 index 0000000..01b0b58 --- /dev/null +++ b/development.supervisord.conf @@ -0,0 +1,17 @@ +[supervisord] +nodaemon=true +user=root + +[program:django] +command=sh -c 'python manage.py runserver 0.0.0.0:8000' +directory=/app +user=django +autostart=true +autorestart=true +redirect_stderr=true + +[program:crond] +command=crond -f +autostart=true +autorestart=true +redirect_stderr=true diff --git a/django.crontab b/django.crontab new file mode 100644 index 0000000..5d88230 --- /dev/null +++ b/django.crontab @@ -0,0 +1 @@ +0 10,13,16,19,22 * * * python /app/manage.py notify_all diff --git a/docs/figures/.$gantt.drawio.bkp b/docs/figures/.$gantt.drawio.bkp deleted file mode 100644 index 2243b3d..0000000 --- a/docs/figures/.$gantt.drawio.bkp +++ /dev/null @@ -1 +0,0 @@ -5ZrLctsgFIafxst2JKHrsnHStItu6s5kTSQs02ChQfjWpy+SkG8HTzKTBOGxFx5xkAB95wA/iAmaLrePAteLX7wgbBJ4xXaC7idBkIVI/beGXW+IorA3lIIWvck/GGb0H9FGT1tXtCDNyY2ScyZpfWrMeVWRXPY2/SwWgm9Ob5tzdlprjUsCDLMcs8H6NTrYn2ghF9rux9kh4weh5UJXngZJn/GM85dS8FWla6x4RfqcJR6K0S1tFrjgmyMTepigqeBc9lfL7ZSwluvAbHhO7oaGTtDdQi6ZSvjqssv+fuFh/y0Pq/cSpDoBeqm8RCNaY7baY1Ppe9LQsgKVYdZa0T0jc1X8XSMFfyFTzrjo8tG0+502ac4rOdNFBLB9+o3WREiyPTLp9j4SviRS7NQtOjcMNUEdlv6Q3hy5ONa2xZF397Gl46rcF30ApC40IzOv1AO8gp7XmjBeL9vXUjWouFHBThpJq/I6IMY2IcKgQy3En5UkpcCSchV5qp4YL2v17v1/OxCRmvFdz/gamKYWmWYwMMMuMLHE3RjLmBpjO7JXgC7wbKKD4Ri16L5VmO0a2hhjkapYFbUgEl8P1MAiVN+DARm3VJ8ElZJUXUwua8quiJ/NiSYA9P7g5gVw6sEM4uacgveaOHj/GGczpuIkz7wwDLDnZXMUZV+CNAOYkjbIfpM1JRtzz8XF31Uj21mkuY6wszk1Q8SZDxB/CLYPIBV7Z8Fo6KBp+EmkQoBlQ4jqoB6U6LY7KeBi6KSJtQBK0kukYCBZJ5U4RQqOZpoUGp1U8oaB3x4pwzJMkwpHJ5X6TpGCw7cmFY1PKnWKFJRcmlQ8OqkscooUukQqGZ2U7wVOobooE1IHUGVOoYLLcI0qGx/VXnC7gSq+hGqYe8ZkFSCnWCUXWY2v1H3klFRPL0p1f3yt7iOnxHoG5QJA5Mq62LCD8GnrYgMpKBecJWXYsrdJCqoFV0mZduhtkoJiwVlShmHKJimoFZwlZXP/zkAKKgVnSY08osONqqAArDYLKsmsxnmb3ghcn2I51Q/tOYY5ZeyIZfyckmfVhrtS4IIqeENedxpDy43+1Ikftjv82jc5ab/NmbyDUObnub3N58CWR5Dha1u0uTWPnK/8DZ0ERfZcAnfebq6TZOeS3aCvLHYSKNmTm/NI9rqOs7aIQh5cGqBb84jvxa8LRmTPJXANcnOdBOyMmpSpxV4CFzvhrU3uYAfWJIH33wXf6ROVPBzg7fKOTkijh/8= \ No newline at end of file diff --git a/docs/figures/.$gantt.drawio.dtmp b/docs/figures/.$gantt.drawio.dtmp deleted file mode 100644 index 2d1232f..0000000 --- a/docs/figures/.$gantt.drawio.dtmp +++ /dev/null @@ -1 +0,0 @@ -5ZrLctsgFIafxst2JKHrsnHStItu6s5kTSws0yChQfjWpy+SkG8HTzKTBOFxFo44SIC+c4AfxARNy+2jwPXyF88JmwRevp2g+0kQ+EEaqX+tZddbkkgbCkFzfdPBMKP/iDZ62rqiOWlObpScM0nrU+OcVxWZy96mn8VC8M3pbQvOTmutcUGAYTbHbLB+jQ72J5rLpbb7cXbI+EFosdSVp0HSZzzj+Ush+KrSNVa8In1OiYdidEubJc755siEHiZoKjiX/VW5nRLWgh2YDc/J3dDQCbpbypKphK8uu+zvFx723/Kwei9BqhOgl8pLNKI1Zqs9NpW+Jw0tKlAZZq0V3TOyUMXfNVLwFzLljIsuH027v9MmLXglZ7qIALZPv9GaCEm2Rybd3kfCSyLFTt2ic8NQE9Rh6Q/pzZGLY21bHnl3H1s6rop90QdA6kIzMvNKPcAr6HmtCeN12b6WqkHFjQp20khaFdcBMbYJEQYdaiH+rCQpBJaUq8hT9cS4rNW797/tQERqxnc942tgmlpkmsHADLvAxBJ3YyxjaoztyF4BusCziQ6GY9Si+1ZhtmtoY4xFqmJV1IJIfD1QA4tQfQ8GZNxSfRJUSlJ1MVnWlF0RP5sTTQDo/cHNC+DUgxnEzTkF7zVx8P4xzmZMxck888IwwJ6XLVCUfQnSDGBK2iD7TdaUbMw9F+d/V41sZ5HmOsLO5tQMEWc+QPwh2D6AVOydBaOhg6bhJ5EKAZYNIaqDelCi2+6kgIuhkybWAihJL5GCgWSdVOIUKTiaaVJodFLJGwZ+e6QMyzBNKhydVOo7RQoO35pUND6p1ClSUHJpUvHopLLIKVLoEqlkdFK+FziF6qJMSB1AlTmFCi7DNapsfFR7we0GqvgSqmHuGZNVgJxilVxkNb5S95FTUj29KNX98bW6j5wS6xmUCwCRK+tiww7Cp62LDaSgXHCWlGHL3iYpqBZcJWXaobdJCooFZ0kZhimbpKBWcJaUzf07AymoFJwlNfKIDjeqghyw2iypJLMaz9v0RuD6FMupfmjPMSwoY0cs4+eUPKs23BUC51TBG/K60xhabvSnTvyw3eHXvpmT9tucyTuthojv7G0+B7Y8ggxf26LNrXnkfOVv6CQosucSuPN2c50kO5fsBn1lsZNAyZ7cnEey13WctUUU8uDSAN2aR3wvfl0wInsugWuQm+skYGfUpEwt9hK42AlvbXIHO7AmCbz/LvhOn6jk4QBvl3d0RBo9/Ac= \ No newline at end of file diff --git a/docs/figures/components.drawio b/docs/figures/components_obsolete.drawio similarity index 100% rename from docs/figures/components.drawio rename to docs/figures/components_obsolete.drawio diff --git a/docs/figures/components.png b/docs/figures/components_obsolete.png similarity index 100% rename from docs/figures/components.png rename to docs/figures/components_obsolete.png diff --git a/docs/figures/datamodel.drawio b/docs/figures/datamodel.drawio index 0ecd6ce..0c5945f 100644 --- a/docs/figures/datamodel.drawio +++ b/docs/figures/datamodel.drawio @@ -1,390 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +7V3dcqO4En6aXJoCJCG4TCaZzKnaqTOVzO6euUoRI9ucAcQATuJ9+hX/SMgYO0BwJrlITFu0pFZ/6m6ppVyAT/7LbWSHm6/UId6FrjovF+D6QtctCNjvlLDLCQjBnLCOXCcnaTXh3v2HFES1oG5dh8RcwYRSL3FDnrikQUCWCUezo4g+88VW1ONrDe01aRHul7bXpv7tOskmp5o6rulfiLvelDVrhpV/49tl4aIn8cZ26HODBG4uwKeI0iT/5L98Il4qu1Iu+Xuf93xbNSwiQdLnhb8WvzaL258geALxZnFNbv3PaKEVo/Fke9uixz5xnt1gHRetTnalKFgHwvSjT51tSruyPXcdMIJHVqwFV3FoL9mLf2RP16w1dYklayOJGOGJRInLpHtZfJHQkFFXNEjui4o09swGM7HdIH3jOmXT7mrR+5QdeWmQiq7fEuqTJNqxIuW3oOhpoYc6KobluR5VhAvapjGiABREu9CkdcW7Fjb7UMj7GNnrLdm3ZL6O6Dbs3/0KB/ZjyUHtFIvFS8XQ21LRTYlUKvENL5WWUOwta8pbKWNL9N1D2VsfocoJfqFJBA8nFbxMGw0vlWXIid74tU2nrCvfjtYuE9ulGr5Uj4tMhpcwIzFRJItC6peV1DOqQ5Y0shOXpl9tA4dEHsN7BvWSP/u0Lv5mrXgsCX/GjE9BZD19FAsyWijSNpFIOaZTuU5dmhltfwu3JYHJuSC50lqkLGw/1b7gMU7/sNrYm/9hIltznXVbUml1jJWqGhLacfxMo2Zzuuq7TyKGmX7VeXacPHg0lVY/5td2Qr67PunH3o0f4m1Iom1jsA9UcMX8AmIH/finfAO7bs2Aklm5ERPNSMwzsee81fztViXHsCO+7XojNDMdvsRercYYOsbbXibuU1/xHsXcYVr68H/KJqO+oOmj1/yUJJgyuRkS7BllpVZe5j+u3HRiz+1U7i8z85M/f7Z910tNyhfiPZGUazr3Jb5XOFajmTPDUiwLGUjXMLA0oArOVtu4aRZUsKkbqmlk7+kSU6eOZuqMw54XcVgcUDzSKNnQNQ1s76amtgagIWcSOJdp8MEeb+7+IRH9Tr/awS71SxI7SurvfDtw/psZPuboMTPoFB4vG59o97+KG3v4kX6joPLx+qVZ8npXPr24SfaaoqqgeM7fBHr5XL+aPjTf/EYipsjJYb87pttoSQ47E6yza9KpacVIpNLu1LOm6qgSv6giRsSzs8mBiwQlGlTU8Y1hPakVeWEZnOoiS1DCvOvFW81wS2DEIwCKbHLJtNgwzbB3jWJhWiDe31woIA2rqLtVQnlDUzvLY/115SEflLIPeQ9r7FZj/Ao44/cOZ5XNrpiHs2XOEc54bnA2eTgbok3pC2eN12sk8hkKz0I9WFzUOVDeOFAeg9eVB5Pg2XzveNY4LOuoE8oTwNaaG2yxAFvtRNgK5giJfAaCLRDX0axuWLXKqwdgKML8yPJoEthaZwNb9TTYLlTmVuu8HZ6jV13GW/PBsyHgWT8Rz0Cu10PjWRPNsGp0ewdC+Unc3nJjYhq81ZjqC7bKd63s248mROR4qRBag/JH087uQ2jgfM6WRaaymGhuCEMCwsRtq96OrggxkdFAEGMVSU38Xksulj9gAnVd7vmPC8ly4Wy+JrBEJWfF4NFGjAfcqYZ1AlOozQ2oQ5lCTfBtAULj2MJyAugZkorlhw4ZHwH84d3Yv/66pc/bYOM/P7hfFqCFuzVN3NWuhb432zd+dcaCWe7DN7eIyziJ01QxyDll3VwqZSSZ3Wa5RXybDf5lGHpsnDIWc9ov5raHB9v/TehPItuLVY/fvZv/ttXRaKqyWAo0WcVCOTfva5J5H48FJlm0PGMwzS7rYhwUvUPV1wzMq77WVn0M25o/RKaRVPO1HoHreGHpK9eA+KXb7pXbw65+Lwe4a/poOsBdNns2/i+EqqI3d/453dQwELzYvu6wUTal5GT03HIdKvZrZ9I+u8nmTTNpXz93QHH/F7adUCAzm/poabNn44Rehu7lcskmn2RWdjPNfBvHdma5adswTaH6LXKnjkZTtVxdhtImbjuhMLUqLTwZYCQ4yVKhZgmnO7KKSLz53gxzZgGoXHTD51CSl9BlPf6AktQwId6pNSSrI5o+UjwnBZIsCWmWQGJGicTxB44+cJSaJGtuOJo0i+AV4WOxrVlFkBf94sc6Zqz2NUfI/DHa0WGXMz2b6BCYvDpiKKhZ33AQ6TwjU9TXkaPBPmsc81BjfJQan66S+ExVEosqeerOOhbW4fB4KilfdmsfrWyp5BQKN/q0OdCyW999567ludlosXgkxxJXd3tnYBlqN6Oh8kMMfj2vnMD35nsIHRTKj5PvMWmi8hsvdeO3zlKem2Fonb059agQEjM5Rkq52tfgvoeFzCJPfH8/1K7y40BQO5v14G8RZbEemVXMHbMosV9cfBNs/X7xdnaElq4eHt0ovbihd9R9DhH3GDeTGECxLA44UBqEQ6Vk3pzNzLHicO1sVoa/EGYO75gG3aVMnFkBrGQpvxvilVsvF42rHto4jDJpDHqOvWaeacXDY+hLuKvHdOC9whpBqAA+1RNgCawBVJAsiW+0w+/a2SxUX1Fn95344QesJ4b1knhLdytbMS+gfU2Wrm97BzrxbqHNMKvy0akus9hsBrAmtdhnk1J4H1L9D/JEvLOF9lE2roVmWacG7MIEU0RIolQdPqz/3inisEc/9fxQhufznx+uPEqdbxGJ42304dVPbf7jXZxQz10++P5mPVy/xGoc1+6uRz6TSHO1f6e5JVswMK36xzwcZZiGUp5NnCbK0GWbTbOca+5IHLr5y/eMy8d0M/l0wxzByE12Pas4tBD5rpEv3Ksn8yoQUnTJgsIQXoV8W7md+D5TpH8lz/EHuidGd9/rYX/veAEgU8G8HZeGDHtWC8cD9/nclHniJndjY7vP9SSjXTPSObfOZs8biPfHnZoMBU+8ZmSwZKhJ74zcm7qRa/w4V0ZC3LyqKru7CnZrt+Sej6y9x+p8py6f35UdwMSKCuoTi/zSLxTv3egNAagqFuJjt6lRMI8EpjFRAEwgogCfBQrUmaEAAqwgTbdMpvGGBlXEqy60LAVrjVUJfBoqgG4pOsOYiphx0RDmMyOA0e+Sm8EAMo/TCD0AcrL7w2Y0ESDGOQBkdncNQx0pCGBVwwgCE1m4DRAkMD0i6VYBqgkrWAgWaFqrId1HeF+gMFALFGdhNWZ3nz7TTUXVmWEoDAd/iV+GCdwwGqfBI821Yc5tZpYAUk3RZkDFav7AadHS49jFWaPlmBtR3gISczuKAQ1d0QCuMIH5Y0GvsRMIpUlnavUjRCpwnBsCkXDjn3Db9LHlj80TZ4/1f1HMi9f/ihLc/As= \ No newline at end of file diff --git a/docs/figures/datamodel.png b/docs/figures/datamodel.png index 2297e4e..798d72e 100644 Binary files a/docs/figures/datamodel.png and b/docs/figures/datamodel.png differ diff --git a/frontend/assets/fonts/notocoloremoji/notocoloremoji-regular.ttf b/frontend/assets/fonts/notocoloremoji/notocoloremoji-regular.ttf deleted file mode 100644 index 8ebcc8f..0000000 Binary files a/frontend/assets/fonts/notocoloremoji/notocoloremoji-regular.ttf and /dev/null differ diff --git a/frontend/assets/images/common/favicon/android-chrome-192x192.png b/frontend/assets/images/common/favicon/android-chrome-192x192.png deleted file mode 100644 index 6b40da6..0000000 Binary files a/frontend/assets/images/common/favicon/android-chrome-192x192.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/android-chrome-512x512.png b/frontend/assets/images/common/favicon/android-chrome-512x512.png deleted file mode 100644 index 6a8698f..0000000 Binary files a/frontend/assets/images/common/favicon/android-chrome-512x512.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/apple-touch-icon.png b/frontend/assets/images/common/favicon/apple-touch-icon.png deleted file mode 100644 index 8396146..0000000 Binary files a/frontend/assets/images/common/favicon/apple-touch-icon.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/browserconfig.xml b/frontend/assets/images/common/favicon/browserconfig.xml deleted file mode 100644 index b3930d0..0000000 --- a/frontend/assets/images/common/favicon/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #da532c - - - diff --git a/frontend/assets/images/common/favicon/favicon-16x16.png b/frontend/assets/images/common/favicon/favicon-16x16.png deleted file mode 100644 index 3534b08..0000000 Binary files a/frontend/assets/images/common/favicon/favicon-16x16.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/favicon-32x32.png b/frontend/assets/images/common/favicon/favicon-32x32.png deleted file mode 100644 index ded72b6..0000000 Binary files a/frontend/assets/images/common/favicon/favicon-32x32.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/favicon.ico b/frontend/assets/images/common/favicon/favicon.ico deleted file mode 100644 index 6400300..0000000 Binary files a/frontend/assets/images/common/favicon/favicon.ico and /dev/null differ diff --git a/frontend/assets/images/common/favicon/favicon.svg b/frontend/assets/images/common/favicon/favicon.svg deleted file mode 100644 index 913aafd..0000000 --- a/frontend/assets/images/common/favicon/favicon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/assets/images/common/favicon/mstile-144x144.png b/frontend/assets/images/common/favicon/mstile-144x144.png deleted file mode 100644 index b56cc9f..0000000 Binary files a/frontend/assets/images/common/favicon/mstile-144x144.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/mstile-150x150.png b/frontend/assets/images/common/favicon/mstile-150x150.png deleted file mode 100644 index 179fb13..0000000 Binary files a/frontend/assets/images/common/favicon/mstile-150x150.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/mstile-310x150.png b/frontend/assets/images/common/favicon/mstile-310x150.png deleted file mode 100644 index 77c5ca9..0000000 Binary files a/frontend/assets/images/common/favicon/mstile-310x150.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/mstile-310x310.png b/frontend/assets/images/common/favicon/mstile-310x310.png deleted file mode 100644 index d099842..0000000 Binary files a/frontend/assets/images/common/favicon/mstile-310x310.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/mstile-70x70.png b/frontend/assets/images/common/favicon/mstile-70x70.png deleted file mode 100644 index e9710d6..0000000 Binary files a/frontend/assets/images/common/favicon/mstile-70x70.png and /dev/null differ diff --git a/frontend/assets/images/common/favicon/safari-pinned-tab.svg b/frontend/assets/images/common/favicon/safari-pinned-tab.svg deleted file mode 100644 index 62ce369..0000000 --- a/frontend/assets/images/common/favicon/safari-pinned-tab.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - -Created by potrace 1.14, written by Peter Selinger 2001-2017 - - - - - diff --git a/frontend/assets/images/common/favicon/site.webmanifest b/frontend/assets/images/common/favicon/site.webmanifest deleted file mode 100644 index a40fa45..0000000 --- a/frontend/assets/images/common/favicon/site.webmanifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "", - "short_name": "", - "icons": [ - { - "src": "./android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "./android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/frontend/assets/images/common/og-image.webp b/frontend/assets/images/common/og-image.webp deleted file mode 100644 index 55357f4..0000000 Binary files a/frontend/assets/images/common/og-image.webp and /dev/null differ diff --git a/frontend/src/css/styles.css b/frontend/src/css/styles.css deleted file mode 100644 index 98ea78f..0000000 --- a/frontend/src/css/styles.css +++ /dev/null @@ -1,4 +0,0 @@ -@import "./fonts.css"; -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html deleted file mode 100644 index d8627d6..0000000 --- a/frontend/src/html/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - Example Site - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - diff --git a/frontend/src/js/App.vue b/frontend/src/js/App.vue deleted file mode 100644 index a14d0c3..0000000 --- a/frontend/src/js/App.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/frontend/src/js/components/ExampleComponent.vue b/frontend/src/js/components/ExampleComponent.vue deleted file mode 100644 index 5e44d6c..0000000 --- a/frontend/src/js/components/ExampleComponent.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/frontend/src/js/components/UserList.vue b/frontend/src/js/components/UserList.vue deleted file mode 100644 index 384a8eb..0000000 --- a/frontend/src/js/components/UserList.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - diff --git a/frontend/src/js/main.js b/frontend/src/js/main.js deleted file mode 100644 index 26af893..0000000 --- a/frontend/src/js/main.js +++ /dev/null @@ -1,12 +0,0 @@ -import { createApp } from 'vue'; -import App from './App.vue'; -import router from './router'; - -// Enable global Vue feature flags: -// https://stackoverflow.com/questions/70083869/parcel-2-vue-3-how-to-set-global-feature-flags-vue-devtools-disabled -globalThis.__VUE_OPTIONS_API__ = true; -globalThis.__VUE_PROD_DEVTOOLS__ = false; - -const app = createApp(App); -app.use(router); -app.mount("#app"); diff --git a/frontend/src/js/router/index.js b/frontend/src/js/router/index.js deleted file mode 100644 index 555bbdc..0000000 --- a/frontend/src/js/router/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import { createRouter, createWebHistory } from 'vue-router'; -import Home from '../views/Home.vue'; - -const routes = [ - { - path: '/', - name: 'Home', - component: Home, - }, - { - path: '/:pathMatch(.*)*', - name: 'NotFound', - component: () => import('../views/NotFound.vue'), - }, -]; - -const router = createRouter({ - history: createWebHistory(process.env.BASE_URL), - routes, -}); - -export default router; diff --git a/frontend/src/js/views/Home.vue b/frontend/src/js/views/Home.vue deleted file mode 100644 index 0c0970f..0000000 --- a/frontend/src/js/views/Home.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/frontend/src/js/views/NotFound.vue b/frontend/src/js/views/NotFound.vue deleted file mode 100644 index 07d777d..0000000 --- a/frontend/src/js/views/NotFound.vue +++ /dev/null @@ -1,15 +0,0 @@ - - - diff --git a/frontend/src/js/views/UserList.vue b/frontend/src/js/views/UserList.vue deleted file mode 100644 index 3261d99..0000000 --- a/frontend/src/js/views/UserList.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/package-lock.json b/package-lock.json index c13f5a0..8914626 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,28 +1,18 @@ { - "name": "MEDWings", + "name": "medwings", "version": "0.0.1", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "MEDWings", + "name": "medwings", "version": "0.0.1", - "license": "UNLICENSED", - "dependencies": { - "axios": "^1.2.2", - "vue": "^3.2.45", - "vue-router": "^4.1.6" - }, + "license": "AGPL-3.0", "devDependencies": { - "@parcel/packager-raw-url": "^2.8.3", - "@parcel/transformer-vue": "^2.8.2", - "@parcel/transformer-webmanifest": "^2.8.3", - "buffer": "^5.7.1", - "parcel": "^2.8.2", - "postcss": "^8.4.20", - "postcss-import": "^15.1.0", - "process": "^0.11.10", - "tailwindcss": "^3.2.4" + "autoprefixer": "^10.4.14", + "parcel": "^2.9.3", + "postcss": "^8.4.26", + "tailwindcss": "^3.3.3" } }, "node_modules/@alloc/quick-lru": { @@ -38,33 +28,33 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -134,17 +124,6 @@ "node": ">=4" } }, - "node_modules/@babel/parser": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", - "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -177,16 +156,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -225,9 +194,9 @@ } }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.5.2.tgz", - "integrity": "sha512-+F8ioQIUN68B4UFiIBYu0QQvgb9FmlKw2ctQMSBfW2QBrZIxz9vD9jCGqTCPqZBRbPHAS/vG1zSXnKqnS2ch/A==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.7.11.tgz", + "integrity": "sha512-r6+vYq2vKzE+vgj/rNVRMwAevq0+ZR9IeMFIqcSga+wMtMdXQ27KqQ7uS99/yXASg29bos7yHP3yk4x6Iio0lw==", "cpu": [ "arm64" ], @@ -238,9 +207,9 @@ ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.5.2.tgz", - "integrity": "sha512-KvPH56KRLLx4KSfKBx0m1r7GGGUMXm0jrKmNE7plbHlesZMuPJICtn07HYgQhj1LNsK7Yqwuvnqh1QxhJnF1EA==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.7.11.tgz", + "integrity": "sha512-jhj1aB4K8ycRL1HOQT5OtzlqOq70jxUQEWRN9Gqh3TIDN30dxXtiHi6EWF516tzw6v2+3QqhDMJh8O6DtTGG8Q==", "cpu": [ "x64" ], @@ -251,9 +220,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.5.2.tgz", - "integrity": "sha512-5kQAP21hAkfW5Bl+e0P57dV4dGYnkNIpR7f/GAh6QHlgXx+vp/teVj4PGRZaKAvt0GX6++N6hF8NnGElLDuIDw==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.7.11.tgz", + "integrity": "sha512-dHfLFVSrw/v5X5lkwp0Vl7+NFpEeEYKfMG2DpdFJnnG1RgHQZngZxCaBagFoaJGykRpd2DYF1AeuXBFrAUAXfw==", "cpu": [ "arm" ], @@ -264,9 +233,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.5.2.tgz", - "integrity": "sha512-aLl89VHL/wjhievEOlPocoefUyWdvzVrcQ/MHQYZm2JfV1jUsrbr/ZfkPPUFvZBf+VSE+Q0clWs9l29PCX1hTQ==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.7.11.tgz", + "integrity": "sha512-7xGEfPPbmVJWcY2Nzqo11B9Nfxs+BAsiiaY/OcT4aaTDdykKeCjvKMQJA3KXCtZ1AtiC9ljyGLi+BfUwdulY5A==", "cpu": [ "arm64" ], @@ -277,9 +246,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.5.2.tgz", - "integrity": "sha512-xUdUfwDJLGjOUPH3BuPBt0NlIrR7f/QHKgu3GZIXswMMIihAekj2i97oI0iWG5Bok/b+OBjHPfa8IU9velnP/Q==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.7.11.tgz", + "integrity": "sha512-vUKI3JrREMQsXX8q0Eq5zX2FlYCKWMmLiCyyJNfZK0Uyf14RBg9VtB3ObQ41b4swYh2EWaltasWVe93Y8+KDng==", "cpu": [ "x64" ], @@ -290,9 +259,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.5.2.tgz", - "integrity": "sha512-zrBczSbXKxEyK2ijtbRdICDygRqWSRPpZMN5dD1T8VMEW5RIhIbwFWw2phDRXuBQdVDpSjalCIUMWMV2h3JaZA==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.7.11.tgz", + "integrity": "sha512-BJwkHlSUgtB+Ei52Ai32M1AOMerSlzyIGA/KC4dAGL+GGwVMdwG8HGCOA2TxP3KjhbgDPMYkv7bt/NmOmRIFng==", "cpu": [ "x64" ], @@ -430,21 +399,21 @@ } }, "node_modules/@parcel/bundler-default": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.8.3.tgz", - "integrity": "sha512-yJvRsNWWu5fVydsWk3O2L4yIy3UZiKWO2cPDukGOIWMgp/Vbpp+2Ct5IygVRtE22bnseW/E/oe0PV3d2IkEJGg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.9.3.tgz", + "integrity": "sha512-JjJK8dq39/UO/MWI/4SCbB1t/qgpQRFnFDetAAAezQ8oN++b24u1fkMDa/xqQGjbuPmGeTds5zxGgYs7id7PYg==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/graph": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/graph": "2.9.3", + "@parcel/hash": "2.9.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -452,15 +421,15 @@ } }, "node_modules/@parcel/cache": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.8.3.tgz", - "integrity": "sha512-k7xv5vSQrJLdXuglo+Hv3yF4BCSs1tQ/8Vbd6CHTkOhf7LcGg6CPtLw053R/KdMpd/4GPn0QrAsOLdATm1ELtQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.9.3.tgz", + "integrity": "sha512-Bj/H2uAJJSXtysG7E/x4EgTrE2hXmm7td/bc97K8M9N7+vQjxf7xb0ebgqe84ePVMkj4MVQSMEJkEucXVx4b0Q==", "dev": true, "dependencies": { - "@parcel/fs": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/utils": "2.8.3", - "lmdb": "2.5.2" + "@parcel/fs": "2.9.3", + "@parcel/logger": "2.9.3", + "@parcel/utils": "2.9.3", + "lmdb": "2.7.11" }, "engines": { "node": ">= 12.0.0" @@ -470,13 +439,13 @@ "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.8.3" + "@parcel/core": "^2.9.3" } }, "node_modules/@parcel/codeframe": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.8.3.tgz", - "integrity": "sha512-FE7sY53D6n/+2Pgg6M9iuEC6F5fvmyBkRE4d9VdnOoxhTXtkEqpqYgX7RJ12FAQwNlxKq4suBJQMgQHMF2Kjeg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.9.3.tgz", + "integrity": "sha512-z7yTyD6h3dvduaFoHpNqur74/2yDWL++33rjQjIjCaXREBN6dKHoMGMizzo/i4vbiI1p9dDox2FIDEHCMQxqdA==", "dev": true, "dependencies": { "chalk": "^4.1.0" @@ -490,16 +459,16 @@ } }, "node_modules/@parcel/compressor-raw": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.8.3.tgz", - "integrity": "sha512-bVDsqleBUxRdKMakWSlWC9ZjOcqDKE60BE+Gh3JSN6WJrycJ02P5wxjTVF4CStNP/G7X17U+nkENxSlMG77ySg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.9.3.tgz", + "integrity": "sha512-jz3t4/ICMsHEqgiTmv5i1DJva2k5QRpZlBELVxfY+QElJTVe8edKJ0TiKcBxh2hx7sm4aUigGmp7JiqqHRRYmA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3" + "@parcel/plugin": "2.9.3" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -507,70 +476,71 @@ } }, "node_modules/@parcel/config-default": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.8.3.tgz", - "integrity": "sha512-o/A/mbrO6X/BfGS65Sib8d6SSG45NYrNooNBkH/o7zbOBSRQxwyTlysleK1/3Wa35YpvFyLOwgfakqCtbGy4fw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.9.3.tgz", + "integrity": "sha512-tqN5tF7QnVABDZAu76co5E6N8mA9n8bxiWdK4xYyINYFIEHgX172oRTqXTnhEMjlMrdmASxvnGlbaPBaVnrCTw==", "dev": true, "dependencies": { - "@parcel/bundler-default": "2.8.3", - "@parcel/compressor-raw": "2.8.3", - "@parcel/namer-default": "2.8.3", - "@parcel/optimizer-css": "2.8.3", - "@parcel/optimizer-htmlnano": "2.8.3", - "@parcel/optimizer-image": "2.8.3", - "@parcel/optimizer-svgo": "2.8.3", - "@parcel/optimizer-terser": "2.8.3", - "@parcel/packager-css": "2.8.3", - "@parcel/packager-html": "2.8.3", - "@parcel/packager-js": "2.8.3", - "@parcel/packager-raw": "2.8.3", - "@parcel/packager-svg": "2.8.3", - "@parcel/reporter-dev-server": "2.8.3", - "@parcel/resolver-default": "2.8.3", - "@parcel/runtime-browser-hmr": "2.8.3", - "@parcel/runtime-js": "2.8.3", - "@parcel/runtime-react-refresh": "2.8.3", - "@parcel/runtime-service-worker": "2.8.3", - "@parcel/transformer-babel": "2.8.3", - "@parcel/transformer-css": "2.8.3", - "@parcel/transformer-html": "2.8.3", - "@parcel/transformer-image": "2.8.3", - "@parcel/transformer-js": "2.8.3", - "@parcel/transformer-json": "2.8.3", - "@parcel/transformer-postcss": "2.8.3", - "@parcel/transformer-posthtml": "2.8.3", - "@parcel/transformer-raw": "2.8.3", - "@parcel/transformer-react-refresh-wrap": "2.8.3", - "@parcel/transformer-svg": "2.8.3" + "@parcel/bundler-default": "2.9.3", + "@parcel/compressor-raw": "2.9.3", + "@parcel/namer-default": "2.9.3", + "@parcel/optimizer-css": "2.9.3", + "@parcel/optimizer-htmlnano": "2.9.3", + "@parcel/optimizer-image": "2.9.3", + "@parcel/optimizer-svgo": "2.9.3", + "@parcel/optimizer-swc": "2.9.3", + "@parcel/packager-css": "2.9.3", + "@parcel/packager-html": "2.9.3", + "@parcel/packager-js": "2.9.3", + "@parcel/packager-raw": "2.9.3", + "@parcel/packager-svg": "2.9.3", + "@parcel/reporter-dev-server": "2.9.3", + "@parcel/resolver-default": "2.9.3", + "@parcel/runtime-browser-hmr": "2.9.3", + "@parcel/runtime-js": "2.9.3", + "@parcel/runtime-react-refresh": "2.9.3", + "@parcel/runtime-service-worker": "2.9.3", + "@parcel/transformer-babel": "2.9.3", + "@parcel/transformer-css": "2.9.3", + "@parcel/transformer-html": "2.9.3", + "@parcel/transformer-image": "2.9.3", + "@parcel/transformer-js": "2.9.3", + "@parcel/transformer-json": "2.9.3", + "@parcel/transformer-postcss": "2.9.3", + "@parcel/transformer-posthtml": "2.9.3", + "@parcel/transformer-raw": "2.9.3", + "@parcel/transformer-react-refresh-wrap": "2.9.3", + "@parcel/transformer-svg": "2.9.3" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.8.3" + "@parcel/core": "^2.9.3" } }, "node_modules/@parcel/core": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.8.3.tgz", - "integrity": "sha512-Euf/un4ZAiClnlUXqPB9phQlKbveU+2CotZv7m7i+qkgvFn5nAGnrV4h1OzQU42j9dpgOxWi7AttUDMrvkbhCQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.9.3.tgz", + "integrity": "sha512-4KlM1Zr/jpsqWuMXr2zmGsaOUs1zMMFh9vfCNKRZkptf+uk8I3sugHbNdo+F5B+4e2yMuOEb1zgAmvJLeuH6ww==", "dev": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", - "@parcel/cache": "2.8.3", - "@parcel/diagnostic": "2.8.3", - "@parcel/events": "2.8.3", - "@parcel/fs": "2.8.3", - "@parcel/graph": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/package-manager": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/cache": "2.9.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/events": "2.9.3", + "@parcel/fs": "2.9.3", + "@parcel/graph": "2.9.3", + "@parcel/hash": "2.9.3", + "@parcel/logger": "2.9.3", + "@parcel/package-manager": "2.9.3", + "@parcel/plugin": "2.9.3", + "@parcel/profiler": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", + "@parcel/types": "2.9.3", + "@parcel/utils": "2.9.3", + "@parcel/workers": "2.9.3", "abortcontroller-polyfill": "^1.1.9", "base-x": "^3.0.8", "browserslist": "^4.6.6", @@ -580,7 +550,7 @@ "json5": "^2.2.0", "msgpackr": "^1.5.4", "nullthrows": "^1.1.1", - "semver": "^5.7.1" + "semver": "^7.5.2" }, "engines": { "node": ">= 12.0.0" @@ -591,9 +561,9 @@ } }, "node_modules/@parcel/diagnostic": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.8.3.tgz", - "integrity": "sha512-u7wSzuMhLGWZjVNYJZq/SOViS3uFG0xwIcqXw12w54Uozd6BH8JlhVtVyAsq9kqnn7YFkw6pXHqAo5Tzh4FqsQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.9.3.tgz", + "integrity": "sha512-6jxBdyB3D7gP4iE66ghUGntWt2v64E6EbD4AetZk+hNJpgudOOPsKTovcMi/i7I4V0qD7WXSF4tvkZUoac0jwA==", "dev": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", @@ -608,9 +578,9 @@ } }, "node_modules/@parcel/events": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.8.3.tgz", - "integrity": "sha512-hoIS4tAxWp8FJk3628bsgKxEvR7bq2scCVYHSqZ4fTi/s0+VymEATrRCUqf+12e5H47uw1/ZjoqrGtBI02pz4w==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.9.3.tgz", + "integrity": "sha512-K0Scx+Bx9f9p1vuShMzNwIgiaZUkxEnexaKYHYemJrM7pMAqxIuIqhnvwurRCsZOVLUJPDDNJ626cWTc5vIq+A==", "dev": true, "engines": { "node": ">= 12.0.0" @@ -621,16 +591,16 @@ } }, "node_modules/@parcel/fs": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.8.3.tgz", - "integrity": "sha512-y+i+oXbT7lP0e0pJZi/YSm1vg0LDsbycFuHZIL80pNwdEppUAtibfJZCp606B7HOjMAlNZOBo48e3hPG3d8jgQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.9.3.tgz", + "integrity": "sha512-/PrRKgCRw22G7rNPSpgN3Q+i2nIkZWuvIOAdMG4KWXC4XLp8C9jarNaWd5QEQ75amjhQSl3oUzABzkdCtkKrgg==", "dev": true, "dependencies": { - "@parcel/fs-search": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/fs-search": "2.9.3", + "@parcel/types": "2.9.3", + "@parcel/utils": "2.9.3", "@parcel/watcher": "^2.0.7", - "@parcel/workers": "2.8.3" + "@parcel/workers": "2.9.3" }, "engines": { "node": ">= 12.0.0" @@ -640,17 +610,14 @@ "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.8.3" + "@parcel/core": "^2.9.3" } }, "node_modules/@parcel/fs-search": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/fs-search/-/fs-search-2.8.3.tgz", - "integrity": "sha512-DJBT2N8knfN7Na6PP2mett3spQLTqxFrvl0gv+TJRp61T8Ljc4VuUTb0hqBj+belaASIp3Q+e8+SgaFQu7wLiQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/fs-search/-/fs-search-2.9.3.tgz", + "integrity": "sha512-nsNz3bsOpwS+jphcd+XjZL3F3PDq9lik0O8HPm5f6LYkqKWT+u/kgQzA8OkAHCR3q96LGiHxUywHPEBc27vI4Q==", "dev": true, - "dependencies": { - "detect-libc": "^1.0.3" - }, "engines": { "node": ">= 12.0.0" }, @@ -660,9 +627,9 @@ } }, "node_modules/@parcel/graph": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-2.8.3.tgz", - "integrity": "sha512-26GL8fYZPdsRhSXCZ0ZWliloK6DHlMJPWh6Z+3VVZ5mnDSbYg/rRKWmrkhnr99ZWmL9rJsv4G74ZwvDEXTMPBg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-2.9.3.tgz", + "integrity": "sha512-3LmRJmF8+OprAr6zJT3X2s8WAhLKkrhi6RsFlMWHifGU5ED1PFcJWFbOwJvSjcAhMQJP0fErcFIK1Ludv3Vm3g==", "dev": true, "dependencies": { "nullthrows": "^1.1.1" @@ -676,12 +643,11 @@ } }, "node_modules/@parcel/hash": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/hash/-/hash-2.8.3.tgz", - "integrity": "sha512-FVItqzjWmnyP4ZsVgX+G00+6U2IzOvqDtdwQIWisCcVoXJFCqZJDy6oa2qDDFz96xCCCynjRjPdQx2jYBCpfYw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/hash/-/hash-2.9.3.tgz", + "integrity": "sha512-qlH5B85XLzVAeijgKPjm1gQu35LoRYX/8igsjnN8vOlbc3O8BYAUIutU58fbHbtE8MJPbxQQUw7tkTjeoujcQQ==", "dev": true, "dependencies": { - "detect-libc": "^1.0.3", "xxhash-wasm": "^0.4.2" }, "engines": { @@ -693,13 +659,13 @@ } }, "node_modules/@parcel/logger": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.8.3.tgz", - "integrity": "sha512-Kpxd3O/Vs7nYJIzkdmB6Bvp3l/85ydIxaZaPfGSGTYOfaffSOTkhcW9l6WemsxUrlts4za6CaEWcc4DOvaMOPA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.9.3.tgz", + "integrity": "sha512-5FNBszcV6ilGFcijEOvoNVG6IUJGsnMiaEnGQs7Fvc1dktTjEddnoQbIYhcSZL63wEmzBZOgkT5yDMajJ/41jw==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/events": "2.8.3" + "@parcel/diagnostic": "2.9.3", + "@parcel/events": "2.9.3" }, "engines": { "node": ">= 12.0.0" @@ -710,9 +676,9 @@ } }, "node_modules/@parcel/markdown-ansi": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.8.3.tgz", - "integrity": "sha512-4v+pjyoh9f5zuU/gJlNvNFGEAb6J90sOBwpKJYJhdWXLZMNFCVzSigxrYO+vCsi8G4rl6/B2c0LcwIMjGPHmFQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.9.3.tgz", + "integrity": "sha512-/Q4X8F2aN8UNjAJrQ5NfK2OmZf6shry9DqetUSEndQ0fHonk78WKt6LT0zSKEBEW/bB/bXk6mNMsCup6L8ibjQ==", "dev": true, "dependencies": { "chalk": "^4.1.0" @@ -726,18 +692,18 @@ } }, "node_modules/@parcel/namer-default": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.8.3.tgz", - "integrity": "sha512-tJ7JehZviS5QwnxbARd8Uh63rkikZdZs1QOyivUhEvhN+DddSAVEdQLHGPzkl3YRk0tjFhbqo+Jci7TpezuAMw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.9.3.tgz", + "integrity": "sha512-1ynFEcap48/Ngzwwn318eLYpLUwijuuZoXQPCsEQ21OOIOtfhFQJaPwXTsw6kRitshKq76P2aafE0BioGSqxcA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -745,15 +711,17 @@ } }, "node_modules/@parcel/node-resolver-core": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-2.8.3.tgz", - "integrity": "sha512-12YryWcA5Iw2WNoEVr/t2HDjYR1iEzbjEcxfh1vaVDdZ020PiGw67g5hyIE/tsnG7SRJ0xdRx1fQ2hDgED+0Ww==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.0.3.tgz", + "integrity": "sha512-AjxNcZVHHJoNT/A99PKIdFtwvoze8PAiC3yz8E/dRggrDIOboUEodeQYV5Aq++aK76uz/iOP0tST2T8A5rhb1A==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/utils": "2.8.3", + "@mischnic/json-sourcemap": "^0.1.0", + "@parcel/diagnostic": "2.9.3", + "@parcel/fs": "2.9.3", + "@parcel/utils": "2.9.3", "nullthrows": "^1.1.1", - "semver": "^5.7.1" + "semver": "^7.5.2" }, "engines": { "node": ">= 12.0.0" @@ -764,22 +732,22 @@ } }, "node_modules/@parcel/optimizer-css": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.8.3.tgz", - "integrity": "sha512-JotGAWo8JhuXsQDK0UkzeQB0UR5hDAKvAviXrjqB4KM9wZNLhLleeEAW4Hk8R9smCeQFP6Xg/N/NkLDpqMwT3g==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.9.3.tgz", + "integrity": "sha512-RK1QwcSdWDNUsFvuLy0hgnYKtPQebzCb0vPPzqs6LhL+vqUu9utOyRycGaQffHCkHVQP6zGlN+KFssd7YtFGhA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", + "@parcel/utils": "2.9.3", "browserslist": "^4.6.6", "lightningcss": "^1.16.1", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -787,12 +755,12 @@ } }, "node_modules/@parcel/optimizer-htmlnano": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.8.3.tgz", - "integrity": "sha512-L8/fHbEy8Id2a2E0fwR5eKGlv9VYDjrH9PwdJE9Za9v1O/vEsfl/0T/79/x129l5O0yB6EFQkFa20MiK3b+vOg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.9.3.tgz", + "integrity": "sha512-9g/KBck3c6DokmJfvJ5zpHFBiCSolaGrcsTGx8C3YPdCTVTI9P1TDCwUxvAr4LjpcIRSa82wlLCI+nF6sSgxKA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", + "@parcel/plugin": "2.9.3", "htmlnano": "^2.0.0", "nullthrows": "^1.1.1", "posthtml": "^0.16.5", @@ -800,7 +768,7 @@ }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -876,40 +844,42 @@ } }, "node_modules/@parcel/optimizer-image": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.8.3.tgz", - "integrity": "sha512-SD71sSH27SkCDNUNx9A3jizqB/WIJr3dsfp+JZGZC42tpD/Siim6Rqy9M4To/BpMMQIIiEXa5ofwS+DgTEiEHQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.9.3.tgz", + "integrity": "sha512-530YzthE7kmecnNhPbkAK+26yQNt69pfJrgE0Ev0BZaM1Wu2+33nki7o8qvkTkikhPrurEJLGIXt1qKmbKvCbA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", - "detect-libc": "^1.0.3" + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", + "@parcel/workers": "2.9.3" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.9.3" } }, "node_modules/@parcel/optimizer-svgo": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.8.3.tgz", - "integrity": "sha512-9KQed99NZnQw3/W4qBYVQ7212rzA9EqrQG019TIWJzkA9tjGBMIm2c/nXpK1tc3hQ3e7KkXkFCQ3C+ibVUnHNA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.9.3.tgz", + "integrity": "sha512-ytQS0wY5JJhWU4mL0wfhYDUuHcfuw+Gy2+JcnTm1t1AZXHlOTbU6EzRWNqBShsgXjvdrQQXizAe3B6GFFlFJVQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", "svgo": "^2.4.0" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -984,22 +954,22 @@ "node": ">=10.13.0" } }, - "node_modules/@parcel/optimizer-terser": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-terser/-/optimizer-terser-2.8.3.tgz", - "integrity": "sha512-9EeQlN6zIeUWwzrzu6Q2pQSaYsYGah8MtiQ/hog9KEPlYTP60hBv/+utDyYEHSQhL7y5ym08tPX5GzBvwAD/dA==", + "node_modules/@parcel/optimizer-swc": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.9.3.tgz", + "integrity": "sha512-GQINNeqtdpL1ombq/Cpwi6IBk02wKJ/JJbYbyfHtk8lxlq13soenpwOlzJ5T9D2fdG+FUhai9NxpN5Ss4lNoAg==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1", - "terser": "^5.2.0" + "@parcel/utils": "2.9.3", + "@swc/core": "^1.3.36", + "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1007,18 +977,19 @@ } }, "node_modules/@parcel/package-manager": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.8.3.tgz", - "integrity": "sha512-tIpY5pD2lH53p9hpi++GsODy6V3khSTX4pLEGuMpeSYbHthnOViobqIlFLsjni+QA1pfc8NNNIQwSNdGjYflVA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.9.3.tgz", + "integrity": "sha512-NH6omcNTEupDmW4Lm1e4NUYBjdqkURxgZ4CNESESInHJe6tblVhNB8Rpr1ar7zDar7cly9ILr8P6N3Ei7bTEjg==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/fs": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", - "semver": "^5.7.1" + "@parcel/diagnostic": "2.9.3", + "@parcel/fs": "2.9.3", + "@parcel/logger": "2.9.3", + "@parcel/node-resolver-core": "3.0.3", + "@parcel/types": "2.9.3", + "@parcel/utils": "2.9.3", + "@parcel/workers": "2.9.3", + "semver": "^7.5.2" }, "engines": { "node": ">= 12.0.0" @@ -1028,23 +999,24 @@ "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.8.3" + "@parcel/core": "^2.9.3" } }, "node_modules/@parcel/packager-css": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.8.3.tgz", - "integrity": "sha512-WyvkMmsurlHG8d8oUVm7S+D+cC/T3qGeqogb7sTI52gB6uiywU7lRCizLNqGFyFGIxcVTVHWnSHqItBcLN76lA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.9.3.tgz", + "integrity": "sha512-mePiWiYZOULY6e1RdAIJyRoYqXqGci0srOaVZYaP7mnrzvJgA63kaZFFsDiEWghunQpMUuUjM2x/vQVHzxmhKQ==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", + "@parcel/utils": "2.9.3", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1052,20 +1024,20 @@ } }, "node_modules/@parcel/packager-html": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.8.3.tgz", - "integrity": "sha512-OhPu1Hx1RRKJodpiu86ZqL8el2Aa4uhBHF6RAL1Pcrh2EhRRlPf70Sk0tC22zUpYL7es+iNKZ/n0Rl+OWSHWEw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.9.3.tgz", + "integrity": "sha512-0Ex+O0EaZf9APNERRNGgGto02hFJ6f5RQEvRWBK55WAV1rXeU+kpjC0c0qZvnUaUtXfpWMsEBkevJCwDkUMeMg==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/plugin": "2.9.3", + "@parcel/types": "2.9.3", + "@parcel/utils": "2.9.3", "nullthrows": "^1.1.1", "posthtml": "^0.16.5" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1073,22 +1045,22 @@ } }, "node_modules/@parcel/packager-js": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.8.3.tgz", - "integrity": "sha512-0pGKC3Ax5vFuxuZCRB+nBucRfFRz4ioie19BbDxYnvBxrd4M3FIu45njf6zbBYsI9eXqaDnL1b3DcZJfYqtIzw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.9.3.tgz", + "integrity": "sha512-V5xwkoE3zQ3R+WqAWhA1KGQ791FvJeW6KonOlMI1q76Djjgox68hhObqcLu66AmYNhR2R/wUpkP18hP2z8dSFw==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/hash": "2.9.3", + "@parcel/plugin": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", + "@parcel/utils": "2.9.3", "globals": "^13.2.0", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1096,34 +1068,16 @@ } }, "node_modules/@parcel/packager-raw": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.8.3.tgz", - "integrity": "sha512-BA6enNQo1RCnco9MhkxGrjOk59O71IZ9DPKu3lCtqqYEVd823tXff2clDKHK25i6cChmeHu6oB1Rb73hlPqhUA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.9.3.tgz", + "integrity": "sha512-oPQTNoYanQ2DdJyL61uPYK2py83rKOT8YVh2QWAx0zsSli6Kiy64U3+xOCYWgDVCrHw9+9NpQMuAdSiFg4cq8g==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3" + "@parcel/plugin": "2.9.3" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/packager-raw-url": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-raw-url/-/packager-raw-url-2.8.3.tgz", - "integrity": "sha512-v8k/x2pnR3DDiIJrPTUcBd+MjH9ocpac0dsdJ02yeyGIQTecqL6nuCtu6W4m75CC7vFjjh/aCK9TJsOHOFnCFQ==", - "dev": true, - "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3" - }, - "engines": { - "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1131,19 +1085,19 @@ } }, "node_modules/@parcel/packager-svg": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.8.3.tgz", - "integrity": "sha512-mvIoHpmv5yzl36OjrklTDFShLUfPFTwrmp1eIwiszGdEBuQaX7JVI3Oo2jbVQgcN4W7J6SENzGQ3Q5hPTW3pMw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.9.3.tgz", + "integrity": "sha512-p/Ya6UO9DAkaCUFxfFGyeHZDp9YPAlpdnh1OChuwqSFOXFjjeXuoK4KLT+ZRalVBo2Jo8xF70oKMZw4MVvaL7Q==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/plugin": "2.9.3", + "@parcel/types": "2.9.3", + "@parcel/utils": "2.9.3", "posthtml": "^0.16.4" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1151,12 +1105,30 @@ } }, "node_modules/@parcel/plugin": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.8.3.tgz", - "integrity": "sha512-jZ6mnsS4D9X9GaNnvrixDQwlUQJCohDX2hGyM0U0bY2NWU8Km97SjtoCpWjq+XBCx/gpC4g58+fk9VQeZq2vlw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.9.3.tgz", + "integrity": "sha512-qN85Gqr2GMuxX1dT1mnuO9hOcvlEv1lrYrCxn7CJN2nUhbwcfG+LEvcrCzCOJ6XtIHm+ZBV9h9p7FfoPLvpw+g==", "dev": true, "dependencies": { - "@parcel/types": "2.8.3" + "@parcel/types": "2.9.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/profiler": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.9.3.tgz", + "integrity": "sha512-pyHc9lw8VZDfgZoeZWZU9J0CVEv1Zw9O5+e0DJPDPHuXJYr72ZAOhbljtU3owWKAeW+++Q2AZWkbUGEOjI/e6g==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.9.3", + "@parcel/events": "2.9.3", + "chrome-trace-event": "^1.0.2" }, "engines": { "node": ">= 12.0.0" @@ -1167,20 +1139,20 @@ } }, "node_modules/@parcel/reporter-cli": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.8.3.tgz", - "integrity": "sha512-3sJkS6tFFzgIOz3u3IpD/RsmRxvOKKiQHOTkiiqRt1l44mMDGKS7zANRnJYsQzdCsgwc9SOP30XFgJwtoVlMbw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.9.3.tgz", + "integrity": "sha512-pZiEvQpuXFuQBafMHxkDmwH8CnnK9sWHwa3bSbsnt385aUahtE8dpY0LKt+K1zfB6degKoczN6aWVj9WycQuZQ==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/plugin": "2.9.3", + "@parcel/types": "2.9.3", + "@parcel/utils": "2.9.3", "chalk": "^4.1.0", "term-size": "^2.2.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1188,17 +1160,37 @@ } }, "node_modules/@parcel/reporter-dev-server": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.8.3.tgz", - "integrity": "sha512-Y8C8hzgzTd13IoWTj+COYXEyCkXfmVJs3//GDBsH22pbtSFMuzAZd+8J9qsCo0EWpiDow7V9f1LischvEh3FbQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.9.3.tgz", + "integrity": "sha512-s6eboxdLEtRSvG52xi9IiNbcPKC0XMVmvTckieue2EqGDbDcaHQoHmmwkk0rNq0/Z/UxelGcQXoIYC/0xq3ykQ==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3" + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-tracer": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.9.3.tgz", + "integrity": "sha512-9cXpKWk0m6d6d+4+TlAdOe8XIPaFEIKGWMWG+5SFAQE08u3olet4PSvd49F4+ZZo5ftRE7YI3j6xNbXvJT8KGw==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", + "chrome-trace-event": "^1.0.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1206,17 +1198,17 @@ } }, "node_modules/@parcel/resolver-default": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.8.3.tgz", - "integrity": "sha512-k0B5M/PJ+3rFbNj4xZSBr6d6HVIe6DH/P3dClLcgBYSXAvElNDfXgtIimbjCyItFkW9/BfcgOVKEEIZOeySH/A==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.9.3.tgz", + "integrity": "sha512-8ESJk1COKvDzkmOnppNXoDamNMlYVIvrKc2RuFPmp8nKVj47R6NwMgvwxEaatyPzvkmyTpq5RvG9I3HFc+r4Cw==", "dev": true, "dependencies": { - "@parcel/node-resolver-core": "2.8.3", - "@parcel/plugin": "2.8.3" + "@parcel/node-resolver-core": "3.0.3", + "@parcel/plugin": "2.9.3" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1224,17 +1216,17 @@ } }, "node_modules/@parcel/runtime-browser-hmr": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.8.3.tgz", - "integrity": "sha512-2O1PYi2j/Q0lTyGNV3JdBYwg4rKo6TEVFlYGdd5wCYU9ZIN9RRuoCnWWH2qCPj3pjIVtBeppYxzfVjPEHINWVg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.9.3.tgz", + "integrity": "sha512-EgiDIDrVAWpz7bOzWXqVinQkaFjLwT34wsonpXAbuI7f7r00d52vNAQC9AMu+pTijA3gyKoJ+Q4NWPMZf7ACDA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3" + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1242,18 +1234,19 @@ } }, "node_modules/@parcel/runtime-js": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.8.3.tgz", - "integrity": "sha512-IRja0vNKwvMtPgIqkBQh0QtRn0XcxNC8HU1jrgWGRckzu10qJWO+5ULgtOeR4pv9krffmMPqywGXw6l/gvJKYQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.9.3.tgz", + "integrity": "sha512-EvIy+qXcKnB5qxHhe96zmJpSAViNVXHfQI5RSdZ2a7CPwORwhTI+zPNT9sb7xb/WwFw/WuTTgzT40b41DceU6Q==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1261,19 +1254,19 @@ } }, "node_modules/@parcel/runtime-react-refresh": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.8.3.tgz", - "integrity": "sha512-2v/qFKp00MfG0234OdOgQNAo6TLENpFYZMbVbAsPMY9ITiqG73MrEsrGXVoGbYiGTMB/Toer/lSWlJxtacOCuA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.9.3.tgz", + "integrity": "sha512-XBgryZQIyCmi6JwEfMUCmINB3l1TpTp9a2iFxmYNpzHlqj4Ve0saKaqWOVRLvC945ZovWIBzcSW2IYqWKGtbAA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", "react-error-overlay": "6.0.9", "react-refresh": "^0.9.0" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1281,18 +1274,18 @@ } }, "node_modules/@parcel/runtime-service-worker": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.8.3.tgz", - "integrity": "sha512-/Skkw+EeRiwzOJso5fQtK8c9b452uWLNhQH1ISTodbmlcyB4YalAiSsyHCtMYD0c3/t5Sx4ZS7vxBAtQd0RvOw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.9.3.tgz", + "integrity": "sha512-qLJLqv1mMdWL7gyh8aKBFFAuEiJkhUUgLKpdn6eSfH/R7kTtb76WnOwqUrhvEI9bZFUM/8Pa1bzJnPpqSOM+Sw==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1312,23 +1305,23 @@ } }, "node_modules/@parcel/transformer-babel": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.8.3.tgz", - "integrity": "sha512-L6lExfpvvC7T/g3pxf3CIJRouQl+sgrSzuWQ0fD4PemUDHvHchSP4SNUVnd6gOytF3Y1KpnEZIunQGi5xVqQCQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.9.3.tgz", + "integrity": "sha512-pURtEsnsp3h6tOBDuzh9wRvVtw4PgIlqwAArIWdrG7iwqOUYv9D8ME4+ePWEu7MQWAp58hv9pTJtqWv4T+Sq8A==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", + "@parcel/utils": "2.9.3", "browserslist": "^4.6.6", "json5": "^2.2.0", "nullthrows": "^1.1.1", - "semver": "^5.7.0" + "semver": "^7.5.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1336,22 +1329,22 @@ } }, "node_modules/@parcel/transformer-css": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.8.3.tgz", - "integrity": "sha512-xTqFwlSXtnaYen9ivAgz+xPW7yRl/u4QxtnDyDpz5dr8gSeOpQYRcjkd4RsYzKsWzZcGtB5EofEk8ayUbWKEUg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.9.3.tgz", + "integrity": "sha512-duWMdbEBBPjg3fQdXF16iWIdThetDZvCs2TpUD7xOlXH6kR0V5BJy8ONFT15u1RCqIV9hSNGaS3v3I9YRNY5zQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", + "@parcel/utils": "2.9.3", "browserslist": "^4.6.6", "lightningcss": "^1.16.1", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1359,24 +1352,24 @@ } }, "node_modules/@parcel/transformer-html": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.8.3.tgz", - "integrity": "sha512-kIZO3qsMYTbSnSpl9cnZog+SwL517ffWH54JeB410OSAYF1ouf4n5v9qBnALZbuCCmPwJRGs4jUtE452hxwN4g==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.9.3.tgz", + "integrity": "sha512-0NU4omcHzFXA1seqftAXA2KNZaMByoKaNdXnLgBgtCGDiYvOcL+6xGHgY6pw9LvOh5um10KI5TxSIMILoI7VtA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/hash": "2.9.3", + "@parcel/plugin": "2.9.3", "nullthrows": "^1.1.1", "posthtml": "^0.16.5", "posthtml-parser": "^0.10.1", "posthtml-render": "^3.0.0", - "semver": "^5.7.1", + "semver": "^7.5.2", "srcset": "4" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1384,66 +1377,65 @@ } }, "node_modules/@parcel/transformer-image": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.8.3.tgz", - "integrity": "sha512-cO4uptcCGTi5H6bvTrAWEFUsTNhA4kCo8BSvRSCHA2sf/4C5tGQPHt3JhdO0GQLPwZRCh/R41EkJs5HZ8A8DAg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.9.3.tgz", + "integrity": "sha512-7CEe35RaPadQzLIuxzTtIxnItvOoy46hcbXtOdDt6lmVa4omuOygZYRIya2lsGIP4JHvAaALMb5nt99a1uTwJg==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", + "@parcel/workers": "2.9.3", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "peerDependencies": { - "@parcel/core": "^2.8.3" + "@parcel/core": "^2.9.3" } }, "node_modules/@parcel/transformer-js": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.8.3.tgz", - "integrity": "sha512-9Qd6bib+sWRcpovvzvxwy/PdFrLUXGfmSW9XcVVG8pvgXsZPFaNjnNT8stzGQj1pQiougCoxMY4aTM5p1lGHEQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.9.3.tgz", + "integrity": "sha512-Z2MVVg5FYcPOfxlUwxqb5l9yjTMEqE3KI3zq2MBRUme6AV07KxLmCDF23b6glzZlHWQUE8MXzYCTAkOPCcPz+Q==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/plugin": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", - "@swc/helpers": "^0.4.12", + "@parcel/utils": "2.9.3", + "@parcel/workers": "2.9.3", + "@swc/helpers": "^0.5.0", "browserslist": "^4.6.6", - "detect-libc": "^1.0.3", "nullthrows": "^1.1.1", "regenerator-runtime": "^0.13.7", - "semver": "^5.7.1" + "semver": "^7.5.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.8.3" + "@parcel/core": "^2.9.3" } }, "node_modules/@parcel/transformer-json": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.8.3.tgz", - "integrity": "sha512-B7LmVq5Q7bZO4ERb6NHtRuUKWGysEeaj9H4zelnyBv+wLgpo4f5FCxSE1/rTNmP9u1qHvQ3scGdK6EdSSokGPg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.9.3.tgz", + "integrity": "sha512-yNL27dbOLhkkrjaQjiQ7Im9VOxmkfuuSNSmS0rA3gEjVcm07SLKRzWkAaPnyx44Lb6bzyOTWwVrb9aMmxgADpA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", + "@parcel/plugin": "2.9.3", "json5": "^2.2.0" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1451,23 +1443,23 @@ } }, "node_modules/@parcel/transformer-postcss": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.8.3.tgz", - "integrity": "sha512-e8luB/poIlz6jBsD1Izms+6ElbyzuoFVa4lFVLZnTAChI3UxPdt9p/uTsIO46HyBps/Bk8ocvt3J4YF84jzmvg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.9.3.tgz", + "integrity": "sha512-HoDvPqKzhpmvMmHqQhDnt8F1vH61m6plpGiYaYnYv2Om4HHi5ZIq9bO+9QLBnTKfaZ7ndYSefTKOxTYElg7wyw==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/hash": "2.9.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", "clone": "^2.1.1", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", - "semver": "^5.7.1" + "semver": "^7.5.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1475,22 +1467,22 @@ } }, "node_modules/@parcel/transformer-posthtml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.8.3.tgz", - "integrity": "sha512-pkzf9Smyeaw4uaRLsT41RGrPLT5Aip8ZPcntawAfIo+KivBQUV0erY1IvHYjyfFzq1ld/Fo2Ith9He6mxpPifA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.9.3.tgz", + "integrity": "sha512-2fQGgrzRmaqbWf3y2/T6xhqrNjzqMMKksqJzvc8TMfK6f2kg3Ddjv158eaSW2JdkV39aY7tvAOn5f1uzo74BMA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", "nullthrows": "^1.1.1", "posthtml": "^0.16.5", "posthtml-parser": "^0.10.1", "posthtml-render": "^3.0.0", - "semver": "^5.7.1" + "semver": "^7.5.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1498,16 +1490,16 @@ } }, "node_modules/@parcel/transformer-raw": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.8.3.tgz", - "integrity": "sha512-G+5cXnd2/1O3nV/pgRxVKZY/HcGSseuhAe71gQdSQftb8uJEURyUHoQ9Eh0JUD3MgWh9V+nIKoyFEZdf9T0sUQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.9.3.tgz", + "integrity": "sha512-oqdPzMC9QzWRbY9J6TZEqltknjno+dY24QWqf8ondmdF2+W+/2mRDu59hhCzQrqUHgTq4FewowRZmSfpzHxwaQ==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3" + "@parcel/plugin": "2.9.3" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1515,18 +1507,18 @@ } }, "node_modules/@parcel/transformer-react-refresh-wrap": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.8.3.tgz", - "integrity": "sha512-q8AAoEvBnCf/nPvgOwFwKZfEl/thwq7c2duxXkhl+tTLDRN2vGmyz4355IxCkavSX+pLWSQ5MexklSEeMkgthg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.9.3.tgz", + "integrity": "sha512-cb9NyU6oJlDblFIlzqIE8AkvRQVGl2IwJNKwD4PdE7Y6sq2okGEPG4hOw3k/Y9JVjM4/2pUORqvjSRhWwd9oVQ==", "dev": true, "dependencies": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/plugin": "2.9.3", + "@parcel/utils": "2.9.3", "react-refresh": "^0.9.0" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1534,66 +1526,23 @@ } }, "node_modules/@parcel/transformer-svg": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.8.3.tgz", - "integrity": "sha512-3Zr/gBzxi1ZH1fftH/+KsZU7w5GqkmxlB0ZM8ovS5E/Pl1lq1t0xvGJue9m2VuQqP8Mxfpl5qLFmsKlhaZdMIQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.9.3.tgz", + "integrity": "sha512-ypmE+dzB09IMCdEAkOsSxq1dEIm2A3h67nAFz4qbfHbwNgXBUuy/jB3ZMwXN/cO0f7SBh/Ap8Jhq6vmGqB5tWw==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/hash": "2.9.3", + "@parcel/plugin": "2.9.3", "nullthrows": "^1.1.1", "posthtml": "^0.16.5", "posthtml-parser": "^0.10.1", "posthtml-render": "^3.0.0", - "semver": "^5.7.1" + "semver": "^7.5.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.8.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-vue": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-vue/-/transformer-vue-2.8.3.tgz", - "integrity": "sha512-GinydFphIuoNZJT7LxFE1mWddcacnQSBXEqnNXad3M9sWpiTSpkiIw5sCbfF4//J9UX8d5FrFB9aECATdkkk5w==", - "dev": true, - "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "@vue/compiler-sfc": "^3.2.27", - "consolidate": "^0.16.0", - "nullthrows": "^1.1.1", - "semver": "^5.7.1" - }, - "engines": { - "node": ">= 12.0.0", - "parcel": "^2.8.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-webmanifest": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-webmanifest/-/transformer-webmanifest-2.8.3.tgz", - "integrity": "sha512-v3NuQc1K1AMfD1+hvh/YYpxBEin52CvEX94MnjwYK6bDmJ35rNuttIs7T6DZxAOopRbGwoTchCShIbv15gTcvg==", - "dev": true, - "dependencies": { - "@mischnic/json-sourcemap": "^0.1.0", - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3" - }, - "engines": { - "parcel": "^2.8.3" + "parcel": "^2.9.3" }, "funding": { "type": "opencollective", @@ -1601,33 +1550,34 @@ } }, "node_modules/@parcel/types": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.8.3.tgz", - "integrity": "sha512-FECA1FB7+0UpITKU0D6TgGBpGxYpVSMNEENZbSJxFSajNy3wrko+zwBKQmFOLOiPcEtnGikxNs+jkFWbPlUAtw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.9.3.tgz", + "integrity": "sha512-NSNY8sYtRhvF1SqhnIGgGvJocyWt1K8Tnw5cVepm0g38ywtX6mwkBvMkmeehXkII4mSUn+frD9wGsydTunezvA==", "dev": true, "dependencies": { - "@parcel/cache": "2.8.3", - "@parcel/diagnostic": "2.8.3", - "@parcel/fs": "2.8.3", - "@parcel/package-manager": "2.8.3", + "@parcel/cache": "2.9.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/fs": "2.9.3", + "@parcel/package-manager": "2.9.3", "@parcel/source-map": "^2.1.1", - "@parcel/workers": "2.8.3", + "@parcel/workers": "2.9.3", "utility-types": "^3.10.0" } }, "node_modules/@parcel/utils": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.8.3.tgz", - "integrity": "sha512-IhVrmNiJ+LOKHcCivG5dnuLGjhPYxQ/IzbnF2DKNQXWBTsYlHkJZpmz7THoeLtLliGmSOZ3ZCsbR8/tJJKmxjA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.9.3.tgz", + "integrity": "sha512-cesanjtj/oLehW8Waq9JFPmAImhoiHX03ihc3JTWkrvJYSbD7wYKCDgPAM3JiRAqvh1LZ6P699uITrYWNoRLUg==", "dev": true, "dependencies": { - "@parcel/codeframe": "2.8.3", - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/markdown-ansi": "2.8.3", + "@parcel/codeframe": "2.9.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/hash": "2.9.3", + "@parcel/logger": "2.9.3", + "@parcel/markdown-ansi": "2.9.3", "@parcel/source-map": "^2.1.1", - "chalk": "^4.1.0" + "chalk": "^4.1.0", + "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0" @@ -1638,36 +1588,248 @@ } }, "node_modules/@parcel/watcher": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.1.0.tgz", - "integrity": "sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.2.0.tgz", + "integrity": "sha512-71S4TF+IMyAn24PK4KSkdKtqJDR3zRzb0HE3yXpacItqTM7XfF2f5q9NEGLEVl0dAaBAGfNwDCjH120y25F6Tg==", "dev": true, "hasInstallScript": true, "dependencies": { + "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", - "node-addon-api": "^3.2.1", - "node-gyp-build": "^4.3.0" + "node-addon-api": "^7.0.0" }, "engines": { "node": ">= 10.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.2.0", + "@parcel/watcher-darwin-arm64": "2.2.0", + "@parcel/watcher-darwin-x64": "2.2.0", + "@parcel/watcher-linux-arm-glibc": "2.2.0", + "@parcel/watcher-linux-arm64-glibc": "2.2.0", + "@parcel/watcher-linux-arm64-musl": "2.2.0", + "@parcel/watcher-linux-x64-glibc": "2.2.0", + "@parcel/watcher-linux-x64-musl": "2.2.0", + "@parcel/watcher-win32-arm64": "2.2.0", + "@parcel/watcher-win32-x64": "2.2.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.2.0.tgz", + "integrity": "sha512-nU2wh00CTQT9rr1TIKTjdQ9lAGYpmz6XuKw0nAwAN+S2A5YiD55BK1u+E5WMCT8YOIDe/n6gaj4o/Bi9294SSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.2.0.tgz", + "integrity": "sha512-cJl0UZDcodciy3TDMomoK/Huxpjlkkim3SyMgWzjovHGOZKNce9guLz2dzuFwfObBFCjfznbFMIvAZ5syXotYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.2.0.tgz", + "integrity": "sha512-QI77zxaGrCV1StKcoRYfsUfmUmvPMPfQrubkBBy5XujV2fwaLgZivQOTQMBgp5K2+E19u1ufpspKXAPqSzpbyg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.2.0.tgz", + "integrity": "sha512-I2GPBcAXazPzabCmfsa3HRRW+MGlqxYd8g8RIueJU+a4o5nyNZDz0CR1cu0INT0QSQXEZV7w6UE8Hz9CF8u3Pg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.2.0.tgz", + "integrity": "sha512-St5mlfp+2lS9AmgixUqfwJa/DwVmTCJxC1HcOubUTz6YFOKIlkHCeUa1Bxi4E/tR/HSez8+heXHL8HQkJ4Bd8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.2.0.tgz", + "integrity": "sha512-jS+qfhhoOBVWwMLP65MaG8xdInMK30pPW8wqTCg2AAuVJh5xepMbzkhHJ4zURqHiyY3EiIRuYu4ONJKCxt8iqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.2.0.tgz", + "integrity": "sha512-xJvJ7R2wJdi47WZBFS691RDOWvP1j/IAs3EXaWVhDI8FFITbWrWaln7KoNcR0Y3T+ZwimFY/cfb0PNht1q895g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.2.0.tgz", + "integrity": "sha512-D+NMpgr23a+RI5mu8ZPKWy7AqjBOkURFDgP5iIXXEf/K3hm0jJ3ogzi0Ed2237B/CdYREimCgXyeiAlE/FtwyA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.2.0.tgz", + "integrity": "sha512-z225cPn3aygJsyVUOWwfyW+fY0Tvk7N3XCOl66qUPFxpbuXeZuiuuJemmtm8vxyqa3Ur7peU/qJxrpC64aeI7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.2.0.tgz", + "integrity": "sha512-JqGW0RJ61BkKx+yYzIURt9s53P7xMVbv0uxYPzAXLBINGaFmkIKSuUPyBVfy8TMbvp93lvF4SPBNDzVRJfvgOw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/@parcel/workers": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.8.3.tgz", - "integrity": "sha512-+AxBnKgjqVpUHBcHLWIHcjYgKIvHIpZjN33mG5LG9XXvrZiqdWvouEzqEXlVLq5VzzVbKIQQcmsvRy138YErkg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.9.3.tgz", + "integrity": "sha512-zRrDuZJzTevrrwElYosFztgldhqW6G9q5zOeQXfVQFkkEJCNfg36ixeiofKRU8uu2x+j+T6216mhMNB6HiuY+w==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "chrome-trace-event": "^1.0.2", + "@parcel/diagnostic": "2.9.3", + "@parcel/logger": "2.9.3", + "@parcel/profiler": "2.9.3", + "@parcel/types": "2.9.3", + "@parcel/utils": "2.9.3", "nullthrows": "^1.1.1" }, "engines": { @@ -1678,13 +1840,207 @@ "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.8.3" + "@parcel/core": "^2.9.3" + } + }, + "node_modules/@swc/core": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.71.tgz", + "integrity": "sha512-T8dqj+SV/S8laW/FGmKHhCGw1o4GRUvJ2jHfbYgEwiJpeutT9uavHvG02t39HJvObBJ52EZs/krGtni4U5928Q==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.71", + "@swc/core-darwin-x64": "1.3.71", + "@swc/core-linux-arm-gnueabihf": "1.3.71", + "@swc/core-linux-arm64-gnu": "1.3.71", + "@swc/core-linux-arm64-musl": "1.3.71", + "@swc/core-linux-x64-gnu": "1.3.71", + "@swc/core-linux-x64-musl": "1.3.71", + "@swc/core-win32-arm64-msvc": "1.3.71", + "@swc/core-win32-ia32-msvc": "1.3.71", + "@swc/core-win32-x64-msvc": "1.3.71" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.71.tgz", + "integrity": "sha512-xOm0hDbcO2ShwQu1CjLtq3fwrG9AvhuE0s8vtBc8AsamYExHmR8bo6GQHJUtfPG1FVPk5a8xoQSd1fs09FQjLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.71.tgz", + "integrity": "sha512-9sbDXBWgM22w/3Ll5kPhXMPkOiHRoqwMOyxLJBfGtIMnFlh5O+NRN3umRerK3pe4Q6/7hj2M5V+crEHYrXmuxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.71.tgz", + "integrity": "sha512-boKdMZsfKvhBs0FDeqH7KQj0lfYe0wCtrL1lv50oYMEeLajY9o4U5xSmc61Sg4HRXjlbR6dlM2cFfL84t7NpAA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.71.tgz", + "integrity": "sha512-yDatyHYMiOVwhyIA/LBwknPs2CUtLYWEMzPZjgLc+56PbgPs3oiEbNWeVUND5onPrfDQgK7NK1y8JeiXZqTgGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.71.tgz", + "integrity": "sha512-xAdCA0L/hoa0ULL5SR4sMZCxkWk7C90DOU7wJalNVG9qNWYICfq3G7AR0E9Ohphzqyahfb5QJED/nA7N0+XwbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.71.tgz", + "integrity": "sha512-j94qLXP/yqhu2afnABAq/xrJIU8TEqcNkp1TlsAeO3R2nVLYL1w4XX8GW71SPnXmd2bwF102c3Cfv/2ilf2y2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.71.tgz", + "integrity": "sha512-YiyU848ql6dLlmt0BHccGAaZ36Cf61VzCAMDKID/gd72snvzWcMCHrwSRW0gEFNXHsjBJrmNl+SLYZHfqoGwUA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.71.tgz", + "integrity": "sha512-1UsJ+6hnIRe/PVdgDPexvgGaN4KpBncT/bAOqlWc9XC7KeBXAWcGA08LrPUz2Ei00DJXzR622IGZVEYOHNkUOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.71.tgz", + "integrity": "sha512-KnuI89+zojR9lDFELdQYZpxzPZ6pBfLwJfWTSGatnpL1ZHhIsV3tK1jwqIdJK1zkRxpBwc6p6FzSZdZwCSpnJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.71", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.71.tgz", + "integrity": "sha512-Pcw7fFirpaBOZsU8fhO48ZCb7NxIjuLnLRPrHqWQ4Mapx1+w9ZNdGya2DKP9n8EAiUrJO20WDsrBNMT2MQSWkA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" } }, "node_modules/@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", + "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", "dev": true, "dependencies": { "tslib": "^2.4.0" @@ -1699,131 +2055,12 @@ "node": ">=10.13.0" } }, - "node_modules/@vue/compiler-core": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz", - "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==", - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.47", - "estree-walker": "^2.0.2", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz", - "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==", - "dependencies": { - "@vue/compiler-core": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz", - "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==", - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.47", - "@vue/compiler-dom": "3.2.47", - "@vue/compiler-ssr": "3.2.47", - "@vue/reactivity-transform": "3.2.47", - "@vue/shared": "3.2.47", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz", - "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==", - "dependencies": { - "@vue/compiler-dom": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", - "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" - }, - "node_modules/@vue/reactivity": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz", - "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==", - "dependencies": { - "@vue/shared": "3.2.47" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz", - "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==", - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.47", - "@vue/shared": "3.2.47", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz", - "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==", - "dependencies": { - "@vue/reactivity": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz", - "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==", - "dependencies": { - "@vue/runtime-core": "3.2.47", - "@vue/shared": "3.2.47", - "csstype": "^2.6.8" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz", - "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==", - "dependencies": { - "@vue/compiler-ssr": "3.2.47", - "@vue/shared": "3.2.47" - }, - "peerDependencies": { - "vue": "3.2.47" - } - }, - "node_modules/@vue/shared": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", - "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==" - }, "node_modules/abortcontroller-polyfill": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", "dev": true }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1870,19 +2107,37 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, "node_modules/balanced-match": { @@ -1900,26 +2155,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1929,12 +2164,6 @@ "node": ">=8" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1964,9 +2193,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "dev": true, "funding": [ { @@ -1976,13 +2205,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" }, "bin": { "browserslist": "cli.js" @@ -1991,36 +2224,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2040,9 +2243,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001486", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz", - "integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==", + "version": "1.0.30001517", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", + "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", "dev": true, "funding": [ { @@ -2150,17 +2353,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -2176,22 +2368,10 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/consolidate": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz", - "integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==", - "dev": true, - "dependencies": { - "bluebird": "^3.7.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "dependencies": { "import-fresh": "^3.2.1", @@ -2365,19 +2545,6 @@ "optional": true, "peer": true }, - "node_modules/csstype": { - "version": "2.6.21", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", - "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -2482,9 +2649,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.389", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.389.tgz", - "integrity": "sha512-WDgWUOK8ROR7sDFyYmxCUOoDc50lPgYAHAHwnnD1iN3SKO/mpqftb9iIPiEkMKmqYdkrR0j3N/O+YB/U7lSxwg==", + "version": "1.4.473", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.473.tgz", + "integrity": "sha512-aVfC8+440vGfl06l8HKKn8/PD5jRfSnLkTTD65EFvU46igbpQRri1gxSzW9/+TeUlwYzrXk1sw867T96zlyECA==", "dev": true }, "node_modules/entities": { @@ -2526,15 +2693,10 @@ "node": ">=0.8.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2580,36 +2742,17 @@ "node": ">=8" } }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, "engines": { - "node": ">=4.0" + "node": "*" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" } }, "node_modules/fs.realpath": { @@ -2781,26 +2924,6 @@ "entities": "^3.0.1" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2852,9 +2975,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -2900,9 +3023,9 @@ } }, "node_modules/jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", + "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -2945,9 +3068,9 @@ } }, "node_modules/lightningcss": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.20.0.tgz", - "integrity": "sha512-4bj8aP+Vi+or8Gwq/hknmicr4PmA8D9uL/3qY0N0daX5vYBMYERGI6Y93nzoeRgQMULq+gtrN/FvJYtH0xNN8g==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.21.5.tgz", + "integrity": "sha512-/pEUPeih2EwIx9n4T82aOG6CInN83tl/mWlw6B5gWLf36UplQi1L+5p3FUHsdt4fXVfOkkh9KIaM3owoq7ss8A==", "dev": true, "dependencies": { "detect-libc": "^1.0.3" @@ -2960,20 +3083,20 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.20.0", - "lightningcss-darwin-x64": "1.20.0", - "lightningcss-linux-arm-gnueabihf": "1.20.0", - "lightningcss-linux-arm64-gnu": "1.20.0", - "lightningcss-linux-arm64-musl": "1.20.0", - "lightningcss-linux-x64-gnu": "1.20.0", - "lightningcss-linux-x64-musl": "1.20.0", - "lightningcss-win32-x64-msvc": "1.20.0" + "lightningcss-darwin-arm64": "1.21.5", + "lightningcss-darwin-x64": "1.21.5", + "lightningcss-linux-arm-gnueabihf": "1.21.5", + "lightningcss-linux-arm64-gnu": "1.21.5", + "lightningcss-linux-arm64-musl": "1.21.5", + "lightningcss-linux-x64-gnu": "1.21.5", + "lightningcss-linux-x64-musl": "1.21.5", + "lightningcss-win32-x64-msvc": "1.21.5" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.20.0.tgz", - "integrity": "sha512-aYEohJTlzwB8URJaNiS57tMbjyLub0mYvxlxKQk8SZv+irXx6MoBWpDNQKKTS9gg1pGf/eAwjpa3BLAoCBsh1A==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.21.5.tgz", + "integrity": "sha512-z05hyLX85WY0UfhkFUOrWEFqD69lpVAmgl3aDzMKlIZJGygbhbegqb4PV8qfUrKKNBauut/qVNPKZglhTaDDxA==", "cpu": [ "arm64" ], @@ -2991,9 +3114,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.20.0.tgz", - "integrity": "sha512-cmMgY8FFWVaGgtift7eKKkHMqlz9O09/yTdlCXEDOeDP9yeo6vHOBTRP7ojb368kjw8Ew3l0L2uT1Gtx56eNkg==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.21.5.tgz", + "integrity": "sha512-MSJhmej/U9MrdPxDk7+FWhO8+UqVoZUHG4VvKT5RQ4RJtqtANTiWiI97LvoVNMtdMnHaKs1Pkji6wHUFxjJsHQ==", "cpu": [ "x64" ], @@ -3011,9 +3134,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.20.0.tgz", - "integrity": "sha512-/m+NDO1O6JCv7R9F0XWlXcintQHx4MPNU+kt8jZJO07LLdGwCfvjN31GVcwVPlStnnx/cU8uTTmax6g/Qu/whg==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.21.5.tgz", + "integrity": "sha512-xN6+5/JsMrbZHL1lPl+MiNJ3Xza12ueBKPepiyDCFQzlhFRTj7D0LG+cfNTzPBTO8KcYQynLpl1iBB8LGp3Xtw==", "cpu": [ "arm" ], @@ -3031,9 +3154,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.20.0.tgz", - "integrity": "sha512-gtXoa6v0HvMRLbev6Hsef0+Q5He7NslB+Rs7G49Y5LUSdJeGIATEN+j8JzHC0DnxCsOGbEgGRmvtJzzYDkkluw==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.21.5.tgz", + "integrity": "sha512-KfzFNhC4XTbmG3ma/xcTs/IhCwieW89XALIusKmnV0N618ZDXEB0XjWOYQRCXeK9mfqPdbTBpurEHV/XZtkniQ==", "cpu": [ "arm64" ], @@ -3051,9 +3174,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.20.0.tgz", - "integrity": "sha512-Po7XpucM1kZnkiyd2BNwTExSDcZ8jm8uB9u+Sq44qjpkf5f75jreQwn3DQm9I1t5C6tB9HGt30HExMju9umJBQ==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.21.5.tgz", + "integrity": "sha512-bc0GytQO5Mn9QM6szaZ+31fQHNdidgpM1sSCwzPItz8hg3wOvKl8039rU0veMJV3ZgC9z0ypNRceLrSHeRHmXw==", "cpu": [ "arm64" ], @@ -3071,9 +3194,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.20.0.tgz", - "integrity": "sha512-8yR/fGNn/P0I+Lc3PK+VWPET/zdSpBfHFIG0DJ38TywMbItVKvnFvoTBwnIm4LqBz7g2G2dDexnNP95za2Ll8g==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.21.5.tgz", + "integrity": "sha512-JwMbgypPQgc2kW2av3OwzZ8cbrEuIiDiXPJdXRE6aVxu67yHauJawQLqJKTGUhiAhy6iLDG8Wg0a3/ziL+m+Kw==", "cpu": [ "x64" ], @@ -3091,9 +3214,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.20.0.tgz", - "integrity": "sha512-EmpJ+VkPZ8RACiB4m+l8TmapmE1W2UvJKDHE+ML/3Ihr9tRKUs3CibfnQTFZC8aSsrxgXagDAN+PgCDDhIyriA==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.21.5.tgz", + "integrity": "sha512-Ib8b6IQ/OR/VrPU6YBgy4T3QnuHY7DUa95O+nz+cwrTkMSN6fuHcTcIaz4t8TJ6HI5pl3uxUOZjmtls2pyQWow==", "cpu": [ "x64" ], @@ -3111,9 +3234,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.20.0.tgz", - "integrity": "sha512-BRdPvbq7Cc1qxAzp2emqWJHrqsEkf4ggxS29VOnxT7jhkdHKU+a26OVMjvm/OL0NH0ToNOZNAPvHMSexiEgBeA==", + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.21.5.tgz", + "integrity": "sha512-A8cSi8lUpBeVmoF+DqqW7cd0FemDbCuKr490IXdjyeI+KL8adpSKUs8tcqO0OXPh1EoDqK7JNkD/dELmd4Iz5g==", "cpu": [ "x64" ], @@ -3146,25 +3269,37 @@ "dev": true }, "node_modules/lmdb": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.5.2.tgz", - "integrity": "sha512-V5V5Xa2Hp9i2XsbDALkBTeHXnBXh/lEmk9p22zdr7jtuOIY9TGhjK6vAvTpOOx9IKU4hJkRWZxn/HsvR1ELLtA==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.7.11.tgz", + "integrity": "sha512-x9bD4hVp7PFLUoELL8RglbNXhAMt5CYhkmss+CEau9KlNoilsTzNi9QDsPZb3KMpOGZXG6jmXhW3bBxE2XVztw==", "dev": true, "hasInstallScript": true, "dependencies": { - "msgpackr": "^1.5.4", + "msgpackr": "1.8.5", "node-addon-api": "^4.3.0", - "node-gyp-build-optional-packages": "5.0.3", - "ordered-binary": "^1.2.4", + "node-gyp-build-optional-packages": "5.0.6", + "ordered-binary": "^1.4.0", "weak-lru-cache": "^1.2.2" }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "2.5.2", - "@lmdb/lmdb-darwin-x64": "2.5.2", - "@lmdb/lmdb-linux-arm": "2.5.2", - "@lmdb/lmdb-linux-arm64": "2.5.2", - "@lmdb/lmdb-linux-x64": "2.5.2", - "@lmdb/lmdb-win32-x64": "2.5.2" + "@lmdb/lmdb-darwin-arm64": "2.7.11", + "@lmdb/lmdb-darwin-x64": "2.7.11", + "@lmdb/lmdb-linux-arm": "2.7.11", + "@lmdb/lmdb-linux-arm64": "2.7.11", + "@lmdb/lmdb-linux-x64": "2.7.11", + "@lmdb/lmdb-win32-x64": "2.7.11" + } + }, + "node_modules/lmdb/node_modules/msgpackr": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.8.5.tgz", + "integrity": "sha512-mpPs3qqTug6ahbblkThoUY2DQdNXcm4IapwOS3Vm/87vmpzLVelvp9h3It1y9l1VPpiFLV11vfOXnmeEwiIXwg==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.1" } }, "node_modules/lmdb/node_modules/node-addon-api": { @@ -3173,12 +3308,16 @@ "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "dev": true }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { - "sourcemap-codec": "^1.4.8" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/mdn-data": { @@ -3211,25 +3350,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3243,9 +3363,9 @@ } }, "node_modules/msgpackr": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.9.1.tgz", - "integrity": "sha512-jJdrNH8tzfCtT0rjPFryBXjRDQE7rqfLkah4/8B4gYa7NNZYFBcGxqWBtfQpGC+oYyBwlkj3fARk4aooKNPHxg==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.9.5.tgz", + "integrity": "sha512-/IJ3cFSN6Ci3eG2wLhbFEL6GT63yEaoN/R5My2QkV6zro+OJaVRLPlwvxY7EtHYSmDlQpk8stvOQTL2qJFkDRg==", "dev": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" @@ -3300,6 +3420,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, "funding": [ { "type": "github", @@ -3314,26 +3435,15 @@ } }, "node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", "dev": true }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-gyp-build-optional-packages": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz", - "integrity": "sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.6.tgz", + "integrity": "sha512-2ZJErHG4du9G3/8IWl/l9Bp5BBFy63rno5GVmjQijvTuUZKsl6g8RB4KH/x3NLcV5ZBb4GsXmAuTYr6dRml3Gw==", "dev": true, "bin": { "node-gyp-build-optional-packages": "bin.js", @@ -3342,9 +3452,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "node_modules/normalize-path": { @@ -3356,6 +3466,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -3402,31 +3521,31 @@ } }, "node_modules/ordered-binary": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.4.0.tgz", - "integrity": "sha512-EHQ/jk4/a9hLupIKxTfUsQRej1Yd/0QLQs3vGvIqg5ZtCYSzNhkzHoZc7Zf4e4kUlDaC3Uw8Q/1opOLNN2OKRQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.4.1.tgz", + "integrity": "sha512-9LtiGlPy982CsgxZvJGNNp2/NnrgEr6EAyN3iIEP3/8vd3YLgAZQHbQ75ZrkfBRGrNg37Dk3U6tuVb+B4Xfslg==", "dev": true }, "node_modules/parcel": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.8.3.tgz", - "integrity": "sha512-5rMBpbNE72g6jZvkdR5gS2nyhwIXaJy8i65osOqs/+5b7zgf3eMKgjSsDrv6bhz3gzifsba6MBJiZdBckl+vnA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.9.3.tgz", + "integrity": "sha512-2GTVocFkwblV/TIg9AmT7TI2fO4xdWkyN8aFUEVtiVNWt96GTR3FgQyHFValfCbcj1k9Xf962Ws2hYXYUr9k1Q==", "dev": true, "dependencies": { - "@parcel/config-default": "2.8.3", - "@parcel/core": "2.8.3", - "@parcel/diagnostic": "2.8.3", - "@parcel/events": "2.8.3", - "@parcel/fs": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/package-manager": "2.8.3", - "@parcel/reporter-cli": "2.8.3", - "@parcel/reporter-dev-server": "2.8.3", - "@parcel/utils": "2.8.3", + "@parcel/config-default": "2.9.3", + "@parcel/core": "2.9.3", + "@parcel/diagnostic": "2.9.3", + "@parcel/events": "2.9.3", + "@parcel/fs": "2.9.3", + "@parcel/logger": "2.9.3", + "@parcel/package-manager": "2.9.3", + "@parcel/reporter-cli": "2.9.3", + "@parcel/reporter-dev-server": "2.9.3", + "@parcel/reporter-tracer": "2.9.3", + "@parcel/utils": "2.9.3", "chalk": "^4.1.0", "commander": "^7.0.0", - "get-port": "^4.2.0", - "v8-compile-cache": "^2.0.0" + "get-port": "^4.2.0" }, "bin": { "parcel": "lib/bin.js" @@ -3496,7 +3615,8 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -3520,18 +3640,19 @@ } }, "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, "engines": { "node": ">= 6" } }, "node_modules/postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "version": "8.4.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", + "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -3591,6 +3712,35 @@ "postcss": "^8.4.21" } }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, "node_modules/postcss-nested": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", @@ -3611,9 +3761,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz", - "integrity": "sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -3678,20 +3828,6 @@ "node": ">=12" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3834,18 +3970,25 @@ ] }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3854,26 +3997,11 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead" - }, "node_modules/srcset": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", @@ -3894,9 +4022,9 @@ "dev": true }, "node_modules/sucrase": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", - "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -3975,9 +4103,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", - "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", + "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -4000,7 +4128,6 @@ "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, @@ -4012,35 +4139,6 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss/node_modules/postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" - }, - "engines": { - "node": ">= 14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, "node_modules/term-size": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", @@ -4053,30 +4151,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser": { - "version": "5.17.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.3.tgz", - "integrity": "sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -4123,9 +4197,9 @@ "dev": true }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", "dev": true }, "node_modules/type-fest": { @@ -4185,38 +4259,6 @@ "node": ">= 4" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/vue": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz", - "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==", - "dependencies": { - "@vue/compiler-dom": "3.2.47", - "@vue/compiler-sfc": "3.2.47", - "@vue/runtime-dom": "3.2.47", - "@vue/server-renderer": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "node_modules/vue-router": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", - "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", - "dependencies": { - "@vue/devtools-api": "^6.4.5" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "vue": "^3.2.0" - } - }, "node_modules/weak-lru-cache": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", @@ -4235,2857 +4277,20 @@ "integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==", "dev": true }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", "dev": true, "engines": { "node": ">= 14" } } - }, - "dependencies": { - "@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true - }, - "@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", - "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==" - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - }, - "dependencies": { - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - } - } - }, - "@lezer/common": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz", - "integrity": "sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig==", - "dev": true - }, - "@lezer/lr": { - "version": "0.15.8", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.15.8.tgz", - "integrity": "sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg==", - "dev": true, - "requires": { - "@lezer/common": "^0.15.0" - } - }, - "@lmdb/lmdb-darwin-arm64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.5.2.tgz", - "integrity": "sha512-+F8ioQIUN68B4UFiIBYu0QQvgb9FmlKw2ctQMSBfW2QBrZIxz9vD9jCGqTCPqZBRbPHAS/vG1zSXnKqnS2ch/A==", - "dev": true, - "optional": true - }, - "@lmdb/lmdb-darwin-x64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.5.2.tgz", - "integrity": "sha512-KvPH56KRLLx4KSfKBx0m1r7GGGUMXm0jrKmNE7plbHlesZMuPJICtn07HYgQhj1LNsK7Yqwuvnqh1QxhJnF1EA==", - "dev": true, - "optional": true - }, - "@lmdb/lmdb-linux-arm": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.5.2.tgz", - "integrity": "sha512-5kQAP21hAkfW5Bl+e0P57dV4dGYnkNIpR7f/GAh6QHlgXx+vp/teVj4PGRZaKAvt0GX6++N6hF8NnGElLDuIDw==", - "dev": true, - "optional": true - }, - "@lmdb/lmdb-linux-arm64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.5.2.tgz", - "integrity": "sha512-aLl89VHL/wjhievEOlPocoefUyWdvzVrcQ/MHQYZm2JfV1jUsrbr/ZfkPPUFvZBf+VSE+Q0clWs9l29PCX1hTQ==", - "dev": true, - "optional": true - }, - "@lmdb/lmdb-linux-x64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.5.2.tgz", - "integrity": "sha512-xUdUfwDJLGjOUPH3BuPBt0NlIrR7f/QHKgu3GZIXswMMIihAekj2i97oI0iWG5Bok/b+OBjHPfa8IU9velnP/Q==", - "dev": true, - "optional": true - }, - "@lmdb/lmdb-win32-x64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.5.2.tgz", - "integrity": "sha512-zrBczSbXKxEyK2ijtbRdICDygRqWSRPpZMN5dD1T8VMEW5RIhIbwFWw2phDRXuBQdVDpSjalCIUMWMV2h3JaZA==", - "dev": true, - "optional": true - }, - "@mischnic/json-sourcemap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz", - "integrity": "sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA==", - "dev": true, - "requires": { - "@lezer/common": "^0.15.7", - "@lezer/lr": "^0.15.4", - "json5": "^2.2.1" - } - }, - "@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", - "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==", - "dev": true, - "optional": true - }, - "@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz", - "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==", - "dev": true, - "optional": true - }, - "@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz", - "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==", - "dev": true, - "optional": true - }, - "@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz", - "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==", - "dev": true, - "optional": true - }, - "@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz", - "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==", - "dev": true, - "optional": true - }, - "@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz", - "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", - "dev": true, - "optional": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@parcel/bundler-default": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.8.3.tgz", - "integrity": "sha512-yJvRsNWWu5fVydsWk3O2L4yIy3UZiKWO2cPDukGOIWMgp/Vbpp+2Ct5IygVRtE22bnseW/E/oe0PV3d2IkEJGg==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/graph": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1" - } - }, - "@parcel/cache": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.8.3.tgz", - "integrity": "sha512-k7xv5vSQrJLdXuglo+Hv3yF4BCSs1tQ/8Vbd6CHTkOhf7LcGg6CPtLw053R/KdMpd/4GPn0QrAsOLdATm1ELtQ==", - "dev": true, - "requires": { - "@parcel/fs": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/utils": "2.8.3", - "lmdb": "2.5.2" - } - }, - "@parcel/codeframe": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.8.3.tgz", - "integrity": "sha512-FE7sY53D6n/+2Pgg6M9iuEC6F5fvmyBkRE4d9VdnOoxhTXtkEqpqYgX7RJ12FAQwNlxKq4suBJQMgQHMF2Kjeg==", - "dev": true, - "requires": { - "chalk": "^4.1.0" - } - }, - "@parcel/compressor-raw": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.8.3.tgz", - "integrity": "sha512-bVDsqleBUxRdKMakWSlWC9ZjOcqDKE60BE+Gh3JSN6WJrycJ02P5wxjTVF4CStNP/G7X17U+nkENxSlMG77ySg==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3" - } - }, - "@parcel/config-default": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.8.3.tgz", - "integrity": "sha512-o/A/mbrO6X/BfGS65Sib8d6SSG45NYrNooNBkH/o7zbOBSRQxwyTlysleK1/3Wa35YpvFyLOwgfakqCtbGy4fw==", - "dev": true, - "requires": { - "@parcel/bundler-default": "2.8.3", - "@parcel/compressor-raw": "2.8.3", - "@parcel/namer-default": "2.8.3", - "@parcel/optimizer-css": "2.8.3", - "@parcel/optimizer-htmlnano": "2.8.3", - "@parcel/optimizer-image": "2.8.3", - "@parcel/optimizer-svgo": "2.8.3", - "@parcel/optimizer-terser": "2.8.3", - "@parcel/packager-css": "2.8.3", - "@parcel/packager-html": "2.8.3", - "@parcel/packager-js": "2.8.3", - "@parcel/packager-raw": "2.8.3", - "@parcel/packager-svg": "2.8.3", - "@parcel/reporter-dev-server": "2.8.3", - "@parcel/resolver-default": "2.8.3", - "@parcel/runtime-browser-hmr": "2.8.3", - "@parcel/runtime-js": "2.8.3", - "@parcel/runtime-react-refresh": "2.8.3", - "@parcel/runtime-service-worker": "2.8.3", - "@parcel/transformer-babel": "2.8.3", - "@parcel/transformer-css": "2.8.3", - "@parcel/transformer-html": "2.8.3", - "@parcel/transformer-image": "2.8.3", - "@parcel/transformer-js": "2.8.3", - "@parcel/transformer-json": "2.8.3", - "@parcel/transformer-postcss": "2.8.3", - "@parcel/transformer-posthtml": "2.8.3", - "@parcel/transformer-raw": "2.8.3", - "@parcel/transformer-react-refresh-wrap": "2.8.3", - "@parcel/transformer-svg": "2.8.3" - } - }, - "@parcel/core": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.8.3.tgz", - "integrity": "sha512-Euf/un4ZAiClnlUXqPB9phQlKbveU+2CotZv7m7i+qkgvFn5nAGnrV4h1OzQU42j9dpgOxWi7AttUDMrvkbhCQ==", - "dev": true, - "requires": { - "@mischnic/json-sourcemap": "^0.1.0", - "@parcel/cache": "2.8.3", - "@parcel/diagnostic": "2.8.3", - "@parcel/events": "2.8.3", - "@parcel/fs": "2.8.3", - "@parcel/graph": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/package-manager": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", - "abortcontroller-polyfill": "^1.1.9", - "base-x": "^3.0.8", - "browserslist": "^4.6.6", - "clone": "^2.1.1", - "dotenv": "^7.0.0", - "dotenv-expand": "^5.1.0", - "json5": "^2.2.0", - "msgpackr": "^1.5.4", - "nullthrows": "^1.1.1", - "semver": "^5.7.1" - } - }, - "@parcel/diagnostic": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.8.3.tgz", - "integrity": "sha512-u7wSzuMhLGWZjVNYJZq/SOViS3uFG0xwIcqXw12w54Uozd6BH8JlhVtVyAsq9kqnn7YFkw6pXHqAo5Tzh4FqsQ==", - "dev": true, - "requires": { - "@mischnic/json-sourcemap": "^0.1.0", - "nullthrows": "^1.1.1" - } - }, - "@parcel/events": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.8.3.tgz", - "integrity": "sha512-hoIS4tAxWp8FJk3628bsgKxEvR7bq2scCVYHSqZ4fTi/s0+VymEATrRCUqf+12e5H47uw1/ZjoqrGtBI02pz4w==", - "dev": true - }, - "@parcel/fs": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.8.3.tgz", - "integrity": "sha512-y+i+oXbT7lP0e0pJZi/YSm1vg0LDsbycFuHZIL80pNwdEppUAtibfJZCp606B7HOjMAlNZOBo48e3hPG3d8jgQ==", - "dev": true, - "requires": { - "@parcel/fs-search": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/watcher": "^2.0.7", - "@parcel/workers": "2.8.3" - } - }, - "@parcel/fs-search": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/fs-search/-/fs-search-2.8.3.tgz", - "integrity": "sha512-DJBT2N8knfN7Na6PP2mett3spQLTqxFrvl0gv+TJRp61T8Ljc4VuUTb0hqBj+belaASIp3Q+e8+SgaFQu7wLiQ==", - "dev": true, - "requires": { - "detect-libc": "^1.0.3" - } - }, - "@parcel/graph": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-2.8.3.tgz", - "integrity": "sha512-26GL8fYZPdsRhSXCZ0ZWliloK6DHlMJPWh6Z+3VVZ5mnDSbYg/rRKWmrkhnr99ZWmL9rJsv4G74ZwvDEXTMPBg==", - "dev": true, - "requires": { - "nullthrows": "^1.1.1" - } - }, - "@parcel/hash": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/hash/-/hash-2.8.3.tgz", - "integrity": "sha512-FVItqzjWmnyP4ZsVgX+G00+6U2IzOvqDtdwQIWisCcVoXJFCqZJDy6oa2qDDFz96xCCCynjRjPdQx2jYBCpfYw==", - "dev": true, - "requires": { - "detect-libc": "^1.0.3", - "xxhash-wasm": "^0.4.2" - } - }, - "@parcel/logger": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.8.3.tgz", - "integrity": "sha512-Kpxd3O/Vs7nYJIzkdmB6Bvp3l/85ydIxaZaPfGSGTYOfaffSOTkhcW9l6WemsxUrlts4za6CaEWcc4DOvaMOPA==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/events": "2.8.3" - } - }, - "@parcel/markdown-ansi": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.8.3.tgz", - "integrity": "sha512-4v+pjyoh9f5zuU/gJlNvNFGEAb6J90sOBwpKJYJhdWXLZMNFCVzSigxrYO+vCsi8G4rl6/B2c0LcwIMjGPHmFQ==", - "dev": true, - "requires": { - "chalk": "^4.1.0" - } - }, - "@parcel/namer-default": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.8.3.tgz", - "integrity": "sha512-tJ7JehZviS5QwnxbARd8Uh63rkikZdZs1QOyivUhEvhN+DddSAVEdQLHGPzkl3YRk0tjFhbqo+Jci7TpezuAMw==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "nullthrows": "^1.1.1" - } - }, - "@parcel/node-resolver-core": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-2.8.3.tgz", - "integrity": "sha512-12YryWcA5Iw2WNoEVr/t2HDjYR1iEzbjEcxfh1vaVDdZ020PiGw67g5hyIE/tsnG7SRJ0xdRx1fQ2hDgED+0Ww==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1", - "semver": "^5.7.1" - } - }, - "@parcel/optimizer-css": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.8.3.tgz", - "integrity": "sha512-JotGAWo8JhuXsQDK0UkzeQB0UR5hDAKvAviXrjqB4KM9wZNLhLleeEAW4Hk8R9smCeQFP6Xg/N/NkLDpqMwT3g==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "browserslist": "^4.6.6", - "lightningcss": "^1.16.1", - "nullthrows": "^1.1.1" - } - }, - "@parcel/optimizer-htmlnano": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.8.3.tgz", - "integrity": "sha512-L8/fHbEy8Id2a2E0fwR5eKGlv9VYDjrH9PwdJE9Za9v1O/vEsfl/0T/79/x129l5O0yB6EFQkFa20MiK3b+vOg==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "htmlnano": "^2.0.0", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5", - "svgo": "^2.4.0" - }, - "dependencies": { - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dev": true, - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dev": true, - "requires": { - "css-tree": "^1.1.2" - } - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "dev": true - }, - "svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "dev": true, - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - } - } - } - }, - "@parcel/optimizer-image": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.8.3.tgz", - "integrity": "sha512-SD71sSH27SkCDNUNx9A3jizqB/WIJr3dsfp+JZGZC42tpD/Siim6Rqy9M4To/BpMMQIIiEXa5ofwS+DgTEiEHQ==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", - "detect-libc": "^1.0.3" - } - }, - "@parcel/optimizer-svgo": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.8.3.tgz", - "integrity": "sha512-9KQed99NZnQw3/W4qBYVQ7212rzA9EqrQG019TIWJzkA9tjGBMIm2c/nXpK1tc3hQ3e7KkXkFCQ3C+ibVUnHNA==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "svgo": "^2.4.0" - }, - "dependencies": { - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dev": true, - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dev": true, - "requires": { - "css-tree": "^1.1.2" - } - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "dev": true - }, - "svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "dev": true, - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - } - } - } - }, - "@parcel/optimizer-terser": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-terser/-/optimizer-terser-2.8.3.tgz", - "integrity": "sha512-9EeQlN6zIeUWwzrzu6Q2pQSaYsYGah8MtiQ/hog9KEPlYTP60hBv/+utDyYEHSQhL7y5ym08tPX5GzBvwAD/dA==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1", - "terser": "^5.2.0" - } - }, - "@parcel/package-manager": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.8.3.tgz", - "integrity": "sha512-tIpY5pD2lH53p9hpi++GsODy6V3khSTX4pLEGuMpeSYbHthnOViobqIlFLsjni+QA1pfc8NNNIQwSNdGjYflVA==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/fs": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", - "semver": "^5.7.1" - } - }, - "@parcel/packager-css": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.8.3.tgz", - "integrity": "sha512-WyvkMmsurlHG8d8oUVm7S+D+cC/T3qGeqogb7sTI52gB6uiywU7lRCizLNqGFyFGIxcVTVHWnSHqItBcLN76lA==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1" - } - }, - "@parcel/packager-html": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.8.3.tgz", - "integrity": "sha512-OhPu1Hx1RRKJodpiu86ZqL8el2Aa4uhBHF6RAL1Pcrh2EhRRlPf70Sk0tC22zUpYL7es+iNKZ/n0Rl+OWSHWEw==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5" - } - }, - "@parcel/packager-js": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.8.3.tgz", - "integrity": "sha512-0pGKC3Ax5vFuxuZCRB+nBucRfFRz4ioie19BbDxYnvBxrd4M3FIu45njf6zbBYsI9eXqaDnL1b3DcZJfYqtIzw==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "globals": "^13.2.0", - "nullthrows": "^1.1.1" - } - }, - "@parcel/packager-raw": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.8.3.tgz", - "integrity": "sha512-BA6enNQo1RCnco9MhkxGrjOk59O71IZ9DPKu3lCtqqYEVd823tXff2clDKHK25i6cChmeHu6oB1Rb73hlPqhUA==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3" - } - }, - "@parcel/packager-raw-url": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-raw-url/-/packager-raw-url-2.8.3.tgz", - "integrity": "sha512-v8k/x2pnR3DDiIJrPTUcBd+MjH9ocpac0dsdJ02yeyGIQTecqL6nuCtu6W4m75CC7vFjjh/aCK9TJsOHOFnCFQ==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3" - } - }, - "@parcel/packager-svg": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.8.3.tgz", - "integrity": "sha512-mvIoHpmv5yzl36OjrklTDFShLUfPFTwrmp1eIwiszGdEBuQaX7JVI3Oo2jbVQgcN4W7J6SENzGQ3Q5hPTW3pMw==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "posthtml": "^0.16.4" - } - }, - "@parcel/plugin": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.8.3.tgz", - "integrity": "sha512-jZ6mnsS4D9X9GaNnvrixDQwlUQJCohDX2hGyM0U0bY2NWU8Km97SjtoCpWjq+XBCx/gpC4g58+fk9VQeZq2vlw==", - "dev": true, - "requires": { - "@parcel/types": "2.8.3" - } - }, - "@parcel/reporter-cli": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.8.3.tgz", - "integrity": "sha512-3sJkS6tFFzgIOz3u3IpD/RsmRxvOKKiQHOTkiiqRt1l44mMDGKS7zANRnJYsQzdCsgwc9SOP30XFgJwtoVlMbw==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "chalk": "^4.1.0", - "term-size": "^2.2.1" - } - }, - "@parcel/reporter-dev-server": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.8.3.tgz", - "integrity": "sha512-Y8C8hzgzTd13IoWTj+COYXEyCkXfmVJs3//GDBsH22pbtSFMuzAZd+8J9qsCo0EWpiDow7V9f1LischvEh3FbQ==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3" - } - }, - "@parcel/resolver-default": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.8.3.tgz", - "integrity": "sha512-k0B5M/PJ+3rFbNj4xZSBr6d6HVIe6DH/P3dClLcgBYSXAvElNDfXgtIimbjCyItFkW9/BfcgOVKEEIZOeySH/A==", - "dev": true, - "requires": { - "@parcel/node-resolver-core": "2.8.3", - "@parcel/plugin": "2.8.3" - } - }, - "@parcel/runtime-browser-hmr": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.8.3.tgz", - "integrity": "sha512-2O1PYi2j/Q0lTyGNV3JdBYwg4rKo6TEVFlYGdd5wCYU9ZIN9RRuoCnWWH2qCPj3pjIVtBeppYxzfVjPEHINWVg==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3" - } - }, - "@parcel/runtime-js": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.8.3.tgz", - "integrity": "sha512-IRja0vNKwvMtPgIqkBQh0QtRn0XcxNC8HU1jrgWGRckzu10qJWO+5ULgtOeR4pv9krffmMPqywGXw6l/gvJKYQ==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1" - } - }, - "@parcel/runtime-react-refresh": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.8.3.tgz", - "integrity": "sha512-2v/qFKp00MfG0234OdOgQNAo6TLENpFYZMbVbAsPMY9ITiqG73MrEsrGXVoGbYiGTMB/Toer/lSWlJxtacOCuA==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "react-error-overlay": "6.0.9", - "react-refresh": "^0.9.0" - } - }, - "@parcel/runtime-service-worker": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.8.3.tgz", - "integrity": "sha512-/Skkw+EeRiwzOJso5fQtK8c9b452uWLNhQH1ISTodbmlcyB4YalAiSsyHCtMYD0c3/t5Sx4ZS7vxBAtQd0RvOw==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1" - } - }, - "@parcel/source-map": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", - "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", - "dev": true, - "requires": { - "detect-libc": "^1.0.3" - } - }, - "@parcel/transformer-babel": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.8.3.tgz", - "integrity": "sha512-L6lExfpvvC7T/g3pxf3CIJRouQl+sgrSzuWQ0fD4PemUDHvHchSP4SNUVnd6gOytF3Y1KpnEZIunQGi5xVqQCQ==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "browserslist": "^4.6.6", - "json5": "^2.2.0", - "nullthrows": "^1.1.1", - "semver": "^5.7.0" - } - }, - "@parcel/transformer-css": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.8.3.tgz", - "integrity": "sha512-xTqFwlSXtnaYen9ivAgz+xPW7yRl/u4QxtnDyDpz5dr8gSeOpQYRcjkd4RsYzKsWzZcGtB5EofEk8ayUbWKEUg==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "browserslist": "^4.6.6", - "lightningcss": "^1.16.1", - "nullthrows": "^1.1.1" - } - }, - "@parcel/transformer-html": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.8.3.tgz", - "integrity": "sha512-kIZO3qsMYTbSnSpl9cnZog+SwL517ffWH54JeB410OSAYF1ouf4n5v9qBnALZbuCCmPwJRGs4jUtE452hxwN4g==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5", - "posthtml-parser": "^0.10.1", - "posthtml-render": "^3.0.0", - "semver": "^5.7.1", - "srcset": "4" - } - }, - "@parcel/transformer-image": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.8.3.tgz", - "integrity": "sha512-cO4uptcCGTi5H6bvTrAWEFUsTNhA4kCo8BSvRSCHA2sf/4C5tGQPHt3JhdO0GQLPwZRCh/R41EkJs5HZ8A8DAg==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", - "nullthrows": "^1.1.1" - } - }, - "@parcel/transformer-js": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.8.3.tgz", - "integrity": "sha512-9Qd6bib+sWRcpovvzvxwy/PdFrLUXGfmSW9XcVVG8pvgXsZPFaNjnNT8stzGQj1pQiougCoxMY4aTM5p1lGHEQ==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "@parcel/workers": "2.8.3", - "@swc/helpers": "^0.4.12", - "browserslist": "^4.6.6", - "detect-libc": "^1.0.3", - "nullthrows": "^1.1.1", - "regenerator-runtime": "^0.13.7", - "semver": "^5.7.1" - } - }, - "@parcel/transformer-json": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.8.3.tgz", - "integrity": "sha512-B7LmVq5Q7bZO4ERb6NHtRuUKWGysEeaj9H4zelnyBv+wLgpo4f5FCxSE1/rTNmP9u1qHvQ3scGdK6EdSSokGPg==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "json5": "^2.2.0" - } - }, - "@parcel/transformer-postcss": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.8.3.tgz", - "integrity": "sha512-e8luB/poIlz6jBsD1Izms+6ElbyzuoFVa4lFVLZnTAChI3UxPdt9p/uTsIO46HyBps/Bk8ocvt3J4YF84jzmvg==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "clone": "^2.1.1", - "nullthrows": "^1.1.1", - "postcss-value-parser": "^4.2.0", - "semver": "^5.7.1" - } - }, - "@parcel/transformer-posthtml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.8.3.tgz", - "integrity": "sha512-pkzf9Smyeaw4uaRLsT41RGrPLT5Aip8ZPcntawAfIo+KivBQUV0erY1IvHYjyfFzq1ld/Fo2Ith9He6mxpPifA==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5", - "posthtml-parser": "^0.10.1", - "posthtml-render": "^3.0.0", - "semver": "^5.7.1" - } - }, - "@parcel/transformer-raw": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.8.3.tgz", - "integrity": "sha512-G+5cXnd2/1O3nV/pgRxVKZY/HcGSseuhAe71gQdSQftb8uJEURyUHoQ9Eh0JUD3MgWh9V+nIKoyFEZdf9T0sUQ==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3" - } - }, - "@parcel/transformer-react-refresh-wrap": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.8.3.tgz", - "integrity": "sha512-q8AAoEvBnCf/nPvgOwFwKZfEl/thwq7c2duxXkhl+tTLDRN2vGmyz4355IxCkavSX+pLWSQ5MexklSEeMkgthg==", - "dev": true, - "requires": { - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3", - "react-refresh": "^0.9.0" - } - }, - "@parcel/transformer-svg": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.8.3.tgz", - "integrity": "sha512-3Zr/gBzxi1ZH1fftH/+KsZU7w5GqkmxlB0ZM8ovS5E/Pl1lq1t0xvGJue9m2VuQqP8Mxfpl5qLFmsKlhaZdMIQ==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/plugin": "2.8.3", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5", - "posthtml-parser": "^0.10.1", - "posthtml-render": "^3.0.0", - "semver": "^5.7.1" - } - }, - "@parcel/transformer-vue": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-vue/-/transformer-vue-2.8.3.tgz", - "integrity": "sha512-GinydFphIuoNZJT7LxFE1mWddcacnQSBXEqnNXad3M9sWpiTSpkiIw5sCbfF4//J9UX8d5FrFB9aECATdkkk5w==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.8.3", - "@vue/compiler-sfc": "^3.2.27", - "consolidate": "^0.16.0", - "nullthrows": "^1.1.1", - "semver": "^5.7.1" - } - }, - "@parcel/transformer-webmanifest": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-webmanifest/-/transformer-webmanifest-2.8.3.tgz", - "integrity": "sha512-v3NuQc1K1AMfD1+hvh/YYpxBEin52CvEX94MnjwYK6bDmJ35rNuttIs7T6DZxAOopRbGwoTchCShIbv15gTcvg==", - "dev": true, - "requires": { - "@mischnic/json-sourcemap": "^0.1.0", - "@parcel/diagnostic": "2.8.3", - "@parcel/plugin": "2.8.3", - "@parcel/utils": "2.8.3" - } - }, - "@parcel/types": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.8.3.tgz", - "integrity": "sha512-FECA1FB7+0UpITKU0D6TgGBpGxYpVSMNEENZbSJxFSajNy3wrko+zwBKQmFOLOiPcEtnGikxNs+jkFWbPlUAtw==", - "dev": true, - "requires": { - "@parcel/cache": "2.8.3", - "@parcel/diagnostic": "2.8.3", - "@parcel/fs": "2.8.3", - "@parcel/package-manager": "2.8.3", - "@parcel/source-map": "^2.1.1", - "@parcel/workers": "2.8.3", - "utility-types": "^3.10.0" - } - }, - "@parcel/utils": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.8.3.tgz", - "integrity": "sha512-IhVrmNiJ+LOKHcCivG5dnuLGjhPYxQ/IzbnF2DKNQXWBTsYlHkJZpmz7THoeLtLliGmSOZ3ZCsbR8/tJJKmxjA==", - "dev": true, - "requires": { - "@parcel/codeframe": "2.8.3", - "@parcel/diagnostic": "2.8.3", - "@parcel/hash": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/markdown-ansi": "2.8.3", - "@parcel/source-map": "^2.1.1", - "chalk": "^4.1.0" - } - }, - "@parcel/watcher": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.1.0.tgz", - "integrity": "sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw==", - "dev": true, - "requires": { - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^3.2.1", - "node-gyp-build": "^4.3.0" - } - }, - "@parcel/workers": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.8.3.tgz", - "integrity": "sha512-+AxBnKgjqVpUHBcHLWIHcjYgKIvHIpZjN33mG5LG9XXvrZiqdWvouEzqEXlVLq5VzzVbKIQQcmsvRy138YErkg==", - "dev": true, - "requires": { - "@parcel/diagnostic": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/types": "2.8.3", - "@parcel/utils": "2.8.3", - "chrome-trace-event": "^1.0.2", - "nullthrows": "^1.1.1" - } - }, - "@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", - "dev": true, - "requires": { - "tslib": "^2.4.0" - } - }, - "@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true - }, - "@vue/compiler-core": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz", - "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==", - "requires": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.47", - "estree-walker": "^2.0.2", - "source-map": "^0.6.1" - } - }, - "@vue/compiler-dom": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz", - "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==", - "requires": { - "@vue/compiler-core": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "@vue/compiler-sfc": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz", - "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==", - "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.47", - "@vue/compiler-dom": "3.2.47", - "@vue/compiler-ssr": "3.2.47", - "@vue/reactivity-transform": "3.2.47", - "@vue/shared": "3.2.47", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - } - }, - "@vue/compiler-ssr": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz", - "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==", - "requires": { - "@vue/compiler-dom": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "@vue/devtools-api": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", - "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" - }, - "@vue/reactivity": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz", - "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==", - "requires": { - "@vue/shared": "3.2.47" - } - }, - "@vue/reactivity-transform": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz", - "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==", - "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.47", - "@vue/shared": "3.2.47", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" - } - }, - "@vue/runtime-core": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz", - "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==", - "requires": { - "@vue/reactivity": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "@vue/runtime-dom": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz", - "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==", - "requires": { - "@vue/runtime-core": "3.2.47", - "@vue/shared": "3.2.47", - "csstype": "^2.6.8" - } - }, - "@vue/server-renderer": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz", - "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==", - "requires": { - "@vue/compiler-ssr": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "@vue/shared": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", - "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==" - }, - "abortcontroller-polyfill": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", - "dev": true - }, - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001486", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz", - "integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "consolidate": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz", - "integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==", - "dev": true, - "requires": { - "bluebird": "^3.7.2" - } - }, - "cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", - "dev": true, - "requires": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" - } - }, - "css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "dependencies": { - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - } - }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "optional": true, - "peer": true - } - } - }, - "css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "css-tree": "~2.2.0" - }, - "dependencies": { - "css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - } - }, - "mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true, - "optional": true, - "peer": true - } - } - }, - "csstype": { - "version": "2.6.21", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", - "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true - }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "dependencies": { - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true - } - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true - }, - "domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "dotenv": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", - "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", - "dev": true - }, - "dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.389", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.389.tgz", - "integrity": "sha512-WDgWUOK8ROR7sDFyYmxCUOoDc50lPgYAHAHwnnD1iN3SKO/mpqftb9iIPiEkMKmqYdkrR0j3N/O+YB/U7lSxwg==", - "dev": true - }, - "entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-port": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", - "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "htmlnano": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.0.4.tgz", - "integrity": "sha512-WGCkyGFwjKW1GeCBsPYacMvaMnZtFJ0zIRnC2NCddkA+IOEhTqskXrS7lep+3yYZw/nQ3dW1UAX4yA/GJyR8BA==", - "dev": true, - "requires": { - "cosmiconfig": "^8.0.0", - "posthtml": "^0.16.5", - "timsort": "^0.3.0" - } - }, - "htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", - "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "lightningcss": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.20.0.tgz", - "integrity": "sha512-4bj8aP+Vi+or8Gwq/hknmicr4PmA8D9uL/3qY0N0daX5vYBMYERGI6Y93nzoeRgQMULq+gtrN/FvJYtH0xNN8g==", - "dev": true, - "requires": { - "detect-libc": "^1.0.3", - "lightningcss-darwin-arm64": "1.20.0", - "lightningcss-darwin-x64": "1.20.0", - "lightningcss-linux-arm-gnueabihf": "1.20.0", - "lightningcss-linux-arm64-gnu": "1.20.0", - "lightningcss-linux-arm64-musl": "1.20.0", - "lightningcss-linux-x64-gnu": "1.20.0", - "lightningcss-linux-x64-musl": "1.20.0", - "lightningcss-win32-x64-msvc": "1.20.0" - } - }, - "lightningcss-darwin-arm64": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.20.0.tgz", - "integrity": "sha512-aYEohJTlzwB8URJaNiS57tMbjyLub0mYvxlxKQk8SZv+irXx6MoBWpDNQKKTS9gg1pGf/eAwjpa3BLAoCBsh1A==", - "dev": true, - "optional": true - }, - "lightningcss-darwin-x64": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.20.0.tgz", - "integrity": "sha512-cmMgY8FFWVaGgtift7eKKkHMqlz9O09/yTdlCXEDOeDP9yeo6vHOBTRP7ojb368kjw8Ew3l0L2uT1Gtx56eNkg==", - "dev": true, - "optional": true - }, - "lightningcss-linux-arm-gnueabihf": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.20.0.tgz", - "integrity": "sha512-/m+NDO1O6JCv7R9F0XWlXcintQHx4MPNU+kt8jZJO07LLdGwCfvjN31GVcwVPlStnnx/cU8uTTmax6g/Qu/whg==", - "dev": true, - "optional": true - }, - "lightningcss-linux-arm64-gnu": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.20.0.tgz", - "integrity": "sha512-gtXoa6v0HvMRLbev6Hsef0+Q5He7NslB+Rs7G49Y5LUSdJeGIATEN+j8JzHC0DnxCsOGbEgGRmvtJzzYDkkluw==", - "dev": true, - "optional": true - }, - "lightningcss-linux-arm64-musl": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.20.0.tgz", - "integrity": "sha512-Po7XpucM1kZnkiyd2BNwTExSDcZ8jm8uB9u+Sq44qjpkf5f75jreQwn3DQm9I1t5C6tB9HGt30HExMju9umJBQ==", - "dev": true, - "optional": true - }, - "lightningcss-linux-x64-gnu": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.20.0.tgz", - "integrity": "sha512-8yR/fGNn/P0I+Lc3PK+VWPET/zdSpBfHFIG0DJ38TywMbItVKvnFvoTBwnIm4LqBz7g2G2dDexnNP95za2Ll8g==", - "dev": true, - "optional": true - }, - "lightningcss-linux-x64-musl": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.20.0.tgz", - "integrity": "sha512-EmpJ+VkPZ8RACiB4m+l8TmapmE1W2UvJKDHE+ML/3Ihr9tRKUs3CibfnQTFZC8aSsrxgXagDAN+PgCDDhIyriA==", - "dev": true, - "optional": true - }, - "lightningcss-win32-x64-msvc": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.20.0.tgz", - "integrity": "sha512-BRdPvbq7Cc1qxAzp2emqWJHrqsEkf4ggxS29VOnxT7jhkdHKU+a26OVMjvm/OL0NH0ToNOZNAPvHMSexiEgBeA==", - "dev": true, - "optional": true - }, - "lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "lmdb": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.5.2.tgz", - "integrity": "sha512-V5V5Xa2Hp9i2XsbDALkBTeHXnBXh/lEmk9p22zdr7jtuOIY9TGhjK6vAvTpOOx9IKU4hJkRWZxn/HsvR1ELLtA==", - "dev": true, - "requires": { - "@lmdb/lmdb-darwin-arm64": "2.5.2", - "@lmdb/lmdb-darwin-x64": "2.5.2", - "@lmdb/lmdb-linux-arm": "2.5.2", - "@lmdb/lmdb-linux-arm64": "2.5.2", - "@lmdb/lmdb-linux-x64": "2.5.2", - "@lmdb/lmdb-win32-x64": "2.5.2", - "msgpackr": "^1.5.4", - "node-addon-api": "^4.3.0", - "node-gyp-build-optional-packages": "5.0.3", - "ordered-binary": "^1.2.4", - "weak-lru-cache": "^1.2.2" - }, - "dependencies": { - "node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", - "dev": true - } - } - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "optional": true, - "peer": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "msgpackr": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.9.1.tgz", - "integrity": "sha512-jJdrNH8tzfCtT0rjPFryBXjRDQE7rqfLkah4/8B4gYa7NNZYFBcGxqWBtfQpGC+oYyBwlkj3fARk4aooKNPHxg==", - "dev": true, - "requires": { - "msgpackr-extract": "^3.0.2" - } - }, - "msgpackr-extract": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz", - "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==", - "dev": true, - "optional": true, - "requires": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2", - "node-gyp-build-optional-packages": "5.0.7" - }, - "dependencies": { - "node-gyp-build-optional-packages": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", - "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", - "dev": true, - "optional": true - } - } - }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" - }, - "node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, - "node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "dev": true - }, - "node-gyp-build-optional-packages": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz", - "integrity": "sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==", - "dev": true - }, - "node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "ordered-binary": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.4.0.tgz", - "integrity": "sha512-EHQ/jk4/a9hLupIKxTfUsQRej1Yd/0QLQs3vGvIqg5ZtCYSzNhkzHoZc7Zf4e4kUlDaC3Uw8Q/1opOLNN2OKRQ==", - "dev": true - }, - "parcel": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.8.3.tgz", - "integrity": "sha512-5rMBpbNE72g6jZvkdR5gS2nyhwIXaJy8i65osOqs/+5b7zgf3eMKgjSsDrv6bhz3gzifsba6MBJiZdBckl+vnA==", - "dev": true, - "requires": { - "@parcel/config-default": "2.8.3", - "@parcel/core": "2.8.3", - "@parcel/diagnostic": "2.8.3", - "@parcel/events": "2.8.3", - "@parcel/fs": "2.8.3", - "@parcel/logger": "2.8.3", - "@parcel/package-manager": "2.8.3", - "@parcel/reporter-cli": "2.8.3", - "@parcel/reporter-dev-server": "2.8.3", - "@parcel/utils": "2.8.3", - "chalk": "^4.1.0", - "commander": "^7.0.0", - "get-port": "^4.2.0", - "v8-compile-cache": "^2.0.0" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", - "requires": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "requires": { - "camelcase-css": "^2.0.1" - } - }, - "postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.11" - } - }, - "postcss-selector-parser": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz", - "integrity": "sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "posthtml": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", - "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", - "dev": true, - "requires": { - "posthtml-parser": "^0.11.0", - "posthtml-render": "^3.0.0" - }, - "dependencies": { - "posthtml-parser": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", - "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", - "dev": true, - "requires": { - "htmlparser2": "^7.1.1" - } - } - } - }, - "posthtml-parser": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz", - "integrity": "sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==", - "dev": true, - "requires": { - "htmlparser2": "^7.1.1" - } - }, - "posthtml-render": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", - "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", - "dev": true, - "requires": { - "is-json": "^2.0.1" - } - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "react-error-overlay": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", - "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", - "dev": true - }, - "react-refresh": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", - "integrity": "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==", - "dev": true - }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "requires": { - "pify": "^2.3.0" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "requires": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "srcset": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", - "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", - "dev": true - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "dev": true - }, - "sucrase": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", - "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "svgo": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", - "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.2.1", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - } - }, - "tailwindcss": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", - "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", - "dev": true, - "requires": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.18.2", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "dependencies": { - "postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" - } - } - } - }, - "term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true - }, - "terser": { - "version": "5.17.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.3.tgz", - "integrity": "sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, - "thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "vue": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz", - "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==", - "requires": { - "@vue/compiler-dom": "3.2.47", - "@vue/compiler-sfc": "3.2.47", - "@vue/runtime-dom": "3.2.47", - "@vue/server-renderer": "3.2.47", - "@vue/shared": "3.2.47" - } - }, - "vue-router": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", - "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", - "requires": { - "@vue/devtools-api": "^6.4.5" - } - }, - "weak-lru-cache": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", - "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "xxhash-wasm": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz", - "integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==", - "dev": true - }, - "yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", - "dev": true - } } } diff --git a/package.json b/package.json index 214b10f..d47672b 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,27 @@ { - "name": "MEDWings", + "name": "medwings", "version": "0.0.1", - "description": "An experimental mobile early warning system for clinical deterioration.", - "scripts": {}, - "private": true, + "description": "Mobile Early Deterioration Warning System.", + "main": "./app/static/dist/index.js", "author": "Julian Lobbes", - "license": "UNLICENSED", + "license": "AGPL-3.0", + "source": "./assets/js/entry.js", + "targets": { + "default": { + "distDir": "./app/static/dist/" + } + }, + "scripts": { + "build": "parcel build --dist-dir app/static/dist assets/main.js", + "start": "parcel --dist-dir app/static/dist --hmr-port 34471 assets/main.js", + "clean": "rm -rvf app/static/dist/*" + }, "devDependencies": { - "@parcel/packager-raw-url": "^2.8.3", - "@parcel/transformer-vue": "^2.8.2", - "@parcel/transformer-webmanifest": "^2.8.3", - "buffer": "^5.7.1", - "parcel": "^2.8.2", - "postcss": "^8.4.20", - "postcss-import": "^15.1.0", - "process": "^0.11.10", - "tailwindcss": "^3.2.4" + "autoprefixer": "^10.4.14", + "parcel": "^2.9.3", + "postcss": "^8.4.26", + "tailwindcss": "^3.3.3" }, "dependencies": { - "axios": "^1.2.2", - "vue": "^3.2.45", - "vue-router": "^4.1.6" } } diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 8ff0fbf..0000000 --- a/postcss.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - plugins: [ - require('postcss-import'), - require('tailwindcss/nesting'), - require('tailwindcss'), - require('autoprefixer'), - ] -}; diff --git a/production.Caddyfile b/production.Caddyfile deleted file mode 100644 index b8bab8a..0000000 --- a/production.Caddyfile +++ /dev/null @@ -1,40 +0,0 @@ -:3000 { - - encode zstd gzip - - @staticfiles { - method GET - path /static/* - } - handle @staticfiles { - file_server { - root /app/public/ - } - } - - handle_path /api/* { - reverse_proxy * backend:3001 - } - - @robots { - method GET - path /robots.txt - path /sitemap.xml - } - handle @robots { - file_server { - root /app/robots/ - } - } - - handle * { - root * /app/dist/ - file_server - try_files {path} / - } - - log { - output stderr - format console - } -} diff --git a/production.backend.Dockerfile b/production.backend.Dockerfile deleted file mode 100644 index b5a6a21..0000000 --- a/production.backend.Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM python:3 - -# Create non-root user -ARG CUSTOM_UID -ARG CUSTOM_GID -ENV CUSTOM_USERNAME=backend -ENV CUSTOM_GROUPNAME=backend -RUN groupadd --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \ - useradd --uid ${CUSTOM_UID:-1000} --gid ${CUSTOM_GID:-1000} --create-home --shell /bin/bash ${CUSTOM_USERNAME} && \ - mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app - -# Copy source files -WORKDIR /app -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} requirements.txt /app/ -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} backend /app/backend/ - -# Install dependencies -RUN pip install -r requirements.txt - -# Run ASGI server -EXPOSE 3001/tcp -USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} -ENTRYPOINT ["uvicorn", "backend.main:app", "--root-path", "/api", "--host", "0.0.0.0", "--port", "3001", "--access-log"] diff --git a/production.docker-compose.yml b/production.docker-compose.yml deleted file mode 100644 index 03a1c8c..0000000 --- a/production.docker-compose.yml +++ /dev/null @@ -1,65 +0,0 @@ ---- - -version: "3" - -services: - frontend: - container_name: frontend - restart: unless-stopped - build: - context: . - dockerfile: ./production.frontend.Dockerfile - args: - CUSTOM_UID: 1000 - CUSTOM_GID: 1000 - networks: - - proxy - - medwings - environment: - TZ: Europe/Berlin - ports: - - "8000:3000" - backend: - container_name: backend - restart: unless-stopped - build: - context: . - dockerfile: ./production.backend.Dockerfile - args: - CUSTOM_UID: 1000 - CUSTOM_GID: 1000 - networks: - - medwings - expose: - - "3001" - environment: - APP_NAME: "MEDWingS" - ADMIN_EMAIL: "admin@example.com" - DEBUG_MODE: "true" - POSTGRES_HOST: "db" - POSTGRES_PORT: "5432" - POSTGRES_DB: "medwings" - POSTGRES_USER: "medwings" - POSTGRES_PASSWORD: "medwings" - db: - image: postgres - container_name: db - restart: unless-stopped - networks: - - medwings - expose: - - "5432" - volumes: - - /srv/medwings/db:/var/lib/postgresql/data - environment: - POSTGRES_DB: "medwings" - POSTGRES_USER: "medwings" - POSTGRES_PASSWORD: "medwings" - -networks: - proxy: - external: true - medwings: - external: false - -... diff --git a/production.frontend.Dockerfile b/production.frontend.Dockerfile deleted file mode 100644 index c984b66..0000000 --- a/production.frontend.Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM debian:latest - -# Install packages -ENV DEBIAN_FRONTEND=noninteractive -RUN apt update && apt install -y curl && \ - curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && apt-get install -y nodejs && \ - apt install -y debian-keyring debian-archive-keyring apt-transport-https && \ - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && \ - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list && \ - apt update && apt install -y caddy && \ - rm -rf /var/lib/apt/lists/* - -# Create non-root user -ARG CUSTOM_UID -ARG CUSTOM_GID -ENV CUSTOM_USERNAME=webserver -ENV CUSTOM_GROUPNAME=webserver -RUN groupadd --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \ - useradd --uid ${CUSTOM_UID:-1000} --gid ${CUSTOM_GID:-1000} --create-home --shell /bin/bash ${CUSTOM_USERNAME} && \ - mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app - -# Copy source files -WORKDIR /app -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} package.json package-lock.json .postcssrc postcss.config.js tailwind.config.js production.Caddyfile /app/ -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} frontend /app/frontend/ -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} public /app/public/ -COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} robots /app/robots/ - -# Install dependencies -USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} -RUN npm install -RUN npx parcel build frontend/src/html/index.html - -ENTRYPOINT ["caddy", "run", "--config", "/app/production.Caddyfile", "--adapter", "caddyfile"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 25e846a..0000000 --- a/requirements.txt +++ /dev/null @@ -1,16 +0,0 @@ -alembic==1.10.4 -anyio==3.6.2 -click==8.1.3 -fastapi==0.95.1 -greenlet==2.0.2 -h11==0.14.0 -idna==3.4 -Mako==1.2.4 -MarkupSafe==2.1.2 -psycopg2-binary==2.9.6 -pydantic==1.10.7 -sniffio==1.3.0 -SQLAlchemy==2.0.13 -starlette==0.26.1 -typing_extensions==4.5.0 -uvicorn==0.22.0 diff --git a/robots/robots.txt b/robots/robots.txt deleted file mode 100644 index 24d33c1..0000000 --- a/robots/robots.txt +++ /dev/null @@ -1,4 +0,0 @@ -User-agent: * -Allow: / - -Sitemap: https://medwings.lobbes.dev/sitemap.xml diff --git a/robots/sitemap.xml b/robots/sitemap.xml deleted file mode 100644 index afe1724..0000000 --- a/robots/sitemap.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - https://medwings.lobbes.dev/index.html - 2023-05-10 - - diff --git a/tailwind.config.js b/tailwind.config.js index e9f68b5..1e320dd 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,28 +1,129 @@ -module.exports = { - content: [ - "./frontend/src/**/*.html", - "./frontend/src/**/*.js", - "./frontend/src/**/*.vue", - ], +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./app/*/templates/**/*.html'], theme: { extend: { fontFamily: { - sans: ["Montserrat", "NotoColorEmoji"], - serif: ["Lora", "NotoColorEmoji"], - mono: ["SourceCodePro", "NotoColorEmoji"], - accent: ["Kanit", "NotoColorEmoji"], - emoji: ["NotoColorEmoji"], + sans: ["Montserrat"], + serif: ["Lora"], + mono: ["SourceCodePro"], + accent: ["Kanit"], + title: ["PlayfairDisplaySC"] }, colors: { - transparent: 'transparent', - current: 'currentColor', - lightshade: '#eeebed', - lightaccent: '#8C919C', - main: '#1C4F97', - darkaccent: '#B3425C', - darkshade: '#221F2F', + transparent: "transparent", + current: "currentColor", + primary: { + 50: "#050511", + 100: "#0F0E35", + 200: "#19165A", + 300: "#23207F", + 400: "#2D2AA3", + 500: "#323a8a", + DEFAULT: "#323a8a", + 600: "#575DA2", + 700: "#7D81BA", + 800: "#A2A4D2", + 900: "#C8C8EB", + }, + secondary: { + 50: "#0A1515", + 100: "#123839", + 200: "#1A5C5E", + 300: "#228083", + 400: "#2AA5A8", + 500: "#4bb1bd", + DEFAULT: "#4bb1bd", + 600: "#71C3C6", + 700: "#97D4D0", + 800: "#BCE6DA", + 900: "#E2F7E5", + }, + accent: { + 50: "#33000b", + 100: "#660015", + 200: "#99001f", + 300: "#cc0029", + 400: "#e04159", + 500: "#e85a6a", + DEFAULT: "#e85a6a", + 600: "#f0747b", + 700: "#f88d8d", + 800: "#fda6a6", + 900: "#ffbfbe", + }, + background: { + 50: "#0D0C0B", + 100: "#28251F", + 200: "#444133", + 300: "#605D48", + 400: "#7C785D", + 500: "#fcfbf8", + DEFAULT: "#fcfbf8", + 600: "#FDFDF6", + 700: "#FDFEFA", + 800: "#FEFFFD", + 900: "#FFFFFF", + }, + text: { + 50: "#0A0A0A", + 100: "#141414", + 200: "#1F1F1F", + 300: "#292929", + 400: "#343434", + 500: "#202120", + DEFAULT: "#202120", + 600: "#5B5B5B", + 700: "#767676", + 800: "#919191", + 900: "#ADADAD", + }, + success: { + 50: "#001400", + 100: "#002800", + 200: "#003D00", + 300: "#005100", + 400: "#006600", + 500: "#00CC00", + DEFAULT: "#00CC00", + 600: "#33D133", + 700: "#66D766", + 800: "#99DD99", + 900: "#CCE3CC", + }, + warning: { + 50: "#191200", + 100: "#332400", + 200: "#4D3600", + 300: "#664800", + 400: "#805A00", + 500: "#EDC000", + DEFAULT: "#EDC000", + 600: "#EFD133", + 700: "#F1E266", + 800: "#F4F399", + 900: "#F7F4CC", + }, + failure: { + 50: "#190000", + 100: "#320000", + 200: "#4C0000", + 300: "#660000", + 400: "#800000", + 500: "#E40000", + DEFAULT: "#E40000", + 600: "#E43333", + 700: "#E46666", + 800: "#E69999", + 900: "#E9CCCC", + }, }, + screens: { + '3xl': '1920px', + '4xl': '3840px', + } }, }, plugins: [], -}; +} +