diff --git a/.gitignore b/.gitignore index 43bb89720..bb20a1fbf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,42 @@ backend/*.pyc backend/logs/*.log backend/*.log +# user image folder images +backend/user_images/*.jpeg +backend/user_images/*.png +backend/user_images/*.jpg + # Frontend -frontend/img/users_img/*.* \ No newline at end of file +frontend/img/users_img/*.* +# Logs +frontend/.gitignore +frontend/logs +frontend/*.log +frontend/npm-debug.log* +frontend/yarn-debug.log* +frontend/yarn-error.log* +frontend/pnpm-debug.log* +frontend/lerna-debug.log* + +frontend/node_modules +frontend/.DS_Store +frontend/dist +frontend/dist-ssr +frontend/coverage +frontend/*.local +frontend/README.md + +frontend/cypress/videos/ +frontend/cypress/screenshots/ + +# Editor directories and files +frontend/.vscode/* +frontend/!.vscode/extensions.json +frontend/.idea +frontend/*.suo +frontend/*.ntvs* +frontend/*.njsproj +frontend/*.sln +frontend/*.sw? + +frontend/*.tsbuildinfo \ No newline at end of file diff --git a/Dockerfile_backend b/Dockerfile_backend index 1659ea097..a62f20001 100644 --- a/Dockerfile_backend +++ b/Dockerfile_backend @@ -1,7 +1,7 @@ FROM python:3.11 # Links Docker image with repository -LABEL org.opencontainers.image.source https://github.com/joaovitoriasilva/gearguardian +LABEL org.opencontainers.image.source https://github.com/joaovitoriasilva/endurain # Set the working directory WORKDIR /app @@ -17,26 +17,33 @@ RUN pip install --no-cache-dir --upgrade -r requirements.txt \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# Make port 80 available to the world outside this container -EXPOSE 80 - # Copy the directory backend contents to /app COPY backend /app -# Define environment variable -ENV DB_HOST="" +# Make port 80 available to the world outside this container +EXPOSE 80 + +# Define environment variables +ENV DB_HOST="mariadb" ENV DB_PORT=3306 -ENV DB_USER="" -ENV DB_PASSWORD="" -ENV DB_DATABASE="" -ENV SECRET_KEY="" +ENV DB_USER="endurain" +ENV DB_PASSWORD="changeme" +ENV DB_DATABASE="endurain" +ENV SECRET_KEY="changeme" ENV ALGORITHM="HS256" ENV ACCESS_TOKEN_EXPIRE_MINUTES=30 -ENV STRAVA_CLIENT_ID="" -ENV STRAVA_CLIENT_SECRET="" -ENV STRAVA_AUTH_CODE="" -ENV JAEGER_HOST="" +ENV STRAVA_CLIENT_ID="changeme" +ENV STRAVA_CLIENT_SECRET="changeme" +ENV STRAVA_AUTH_CODE="changeme" +ENV JAEGER_ENABLED="true" +ENV JAEGER_HOST="jaeger" +ENV JAEGER_PROTOCOL="http" +ENV JAGGER_PORT=4317 ENV STRAVA_DAYS_ACTIVITIES_ONLINK=30 +ENV FRONTEND_PROTOCOL="http" +ENV FRONTEND_HOST="frontend" +ENV FRONTEND_PORT=8080 +ENV GEOCODES_MAPS_API="changeme" # Run main.py when the container launches -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] \ No newline at end of file diff --git a/Dockerfile_frontend b/Dockerfile_frontend index 0e2024998..a23a967fe 100644 --- a/Dockerfile_frontend +++ b/Dockerfile_frontend @@ -1,38 +1,34 @@ -# Use an official PHP runtime as a parent image -FROM php:8.3-apache +# Use an official node runtime as a parent image +FROM node:20-alpine as build-stage # Links Docker image with repository -LABEL org.opencontainers.image.source https://github.com/joaovitoriasilva/gearguardian +LABEL org.opencontainers.image.source https://github.com/joaovitoriasilva/endurain -# Set the working directory to /var/www/html -WORKDIR /var/www/html +# Set the working directory to /app +WORKDIR /app -# Copy the current directory contents into the container at /var/www/html -COPY frontend/ /var/www/html +# Copy package.json and package-lock.json +COPY frontend/package*.json ./ +RUN npm install -# Copy custom php.ini -COPY custom_php.ini /usr/local/etc/php/php.ini +# Copy the current directory contents into the container at /app +COPY frontend ./ -# Install any dependencies your application needs -RUN apt-get update +# Build the app +RUN npm run build -# Change ownership of the directory to www-data:www-data -RUN chown -R www-data:www-data /var/www/html +# Use nginx to serve the built app +FROM nginx:alpine as production-stage -# Change permissions of the directory to 755 -RUN chmod -R 755 /var/www/html +COPY --from=build-stage /app/dist /usr/share/nginx/html +COPY nginx-custom.conf /etc/nginx/conf.d/default.conf + +COPY frontend_env.sh /docker-entrypoint.d/frontend_env.sh +RUN chmod +x /docker-entrypoint.d/frontend_env.sh -# Expose port 80 to the outside world EXPOSE 80 -# Define environment variables -ENV APACHE_DOCUMENT_ROOT /var/www/html +ENV MY_APP_BACKEND_PROTOCOL=http +ENV MY_APP_BACKEND_HOST=localhost:98 -# Enable Apache modules -RUN a2enmod rewrite - -# Update the default virtual host to use the environment variable -RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf - -# Start Apache -CMD ["apache2-foreground"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 3bbddc73e..3e34f3a82 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@
- + # Endurain + + A self-hosted fitness tracking service • Endurain Mastodon profile @@ -11,7 +13,7 @@ > [!WARNING] > This project is currently in **Alpha** state. You can try it out at your own risk, but be aware that things might break and **DATA LOSS** may occur. -Endurain is a self-hosted fitness tracking service that operates much like Strava but allows users to have complete control over their data and the hosting environment. The application's frontend is built using a combination of PHP, HTML, basic JavaScript, and Bootstrap CSS. On the backend, it leverages Python FastAPI, Alembic, SQLAlchemy, stravalib and gpxpy for seamless integration with Strava and .gpx file import. The MariaDB database engine is employed to efficiently store and manage user data, while Jaeger is used for basic observability. +Endurain is a self-hosted fitness tracking service that operates much like Strava but allows users to have complete control over their data and the hosting environment. The application's frontend is built using Vue.js and Bootstrap CSS. On the backend, it leverages Python FastAPI, Alembic, SQLAlchemy, stravalib and gpxpy for seamless integration with Strava and .gpx file import. The MariaDB database engine is employed to efficiently store and manage user data, while Jaeger is used for basic observability. To deploy Endurain, Docker images are available, and a comprehensive example can be found in the "docker-compose.yml" file provided. Configuration is facilitated through environment variables, ensuring flexibility and ease of customization. @@ -55,76 +57,77 @@ More screenshots: https://imgur.com/a/lDR0sBf # Frontend Table bellow shows supported environemnt variables. Variables marked with optional "No" should be set to avoid errors. -Environemnt variable | Default value | Optional ---- | --- | --- -BACKEND_PROTOCOL* | http | Yes -BACKEND_HOST** | backend | Yes - -*BACKEND_PROTOCOL needs to be https if you want to enable Strava integration -**BACKEND_HOST needs to be set and be Internet faced/resolved if you want to enable Strava integration. Strava callback relies on this. +Environemnt variable | Default value | Optional | Notes +--- | --- | --- | --- +MY_APP_BACKEND_PROTOCOL | http | Yes | Needs to be https if you want to enable Strava integration. You may need to update this variable based on docker image spin up +MY_APP_BACKEND_HOST | localhost:98 | Yes | Needs to be set and be Internet faced/resolved if you want to enable Strava integration. Strava callback relies on this. You may need to update this variable based on docker image spin up Frontend dependencies: - - php:8.3-apache + - vue@3.4.24 + - vue-router@4.3.2 + - vue-i18n@9.13.1 + - vite@5.2.10 + - pinia@2.1.7 + - crypto-js@4.2.0 + - chart.js@4.4.2 - User avatars create using DiceBear (https://www.dicebear.com) avataaars style. - - Bootstrap CSS v5.3.2 - - leaflet v1.7.1 - - fontawesome icons free version + - Bootstrap CSS v5.3.3 + - leaflet v1.9.4 + - fontawesome icons free version@6.5.2 and vue-fontawesome@3.0.6 - Logo created using Canvas --- # Backend Table bellow shows supported environemnt variables. Variables marked with optional "No" should be set to avoid errors. -Environemnt variable | Default value | Optional ---- | --- | --- -DB_HOST | mariadb | Yes -DB_PORT | 3306 | Yes -DB_USER | gearguardian | Yes -DB_PASSWORD | changeme | `No` -DB_DATABASE | gearguardian | Yes -SECRET_KEY | changeme | `No` -ALGORITHM | HS256 | Yes -ACCESS_TOKEN_EXPIRE_MINUTES | 30 | Yes -STRAVA_CLIENT_ID | changeme | `No` -STRAVA_CLIENT_SECRET | changeme | `No` -STRAVA_AUTH_CODE | changeme | `No` -JAEGER_ENABLED | true | Yes -JAEGER_PROTOCOL | http | Yes -JAEGER_HOST | jaeger | Yes -JAGGER_PORT | 4317 | Yes -STRAVA_DAYS_ACTIVITIES_ONLINK | 30 | Yes -FRONTEND_HOST* | frontend | Yes -GEOCODES_MAPS_API** | changeme | `No` - -*FRONTEND_HOST needs to be set if you want to enable Strava integration -**Geocode maps offers a free plan consisting of 1 Request/Second. Registration necessary. +Environemnt variable | Default value | Optional | Notes +--- | --- | --- | --- +DB_HOST | mariadb | Yes | N/A +DB_PORT | 3306 | Yes | N/A +DB_USER | gearguardian | Yes | N/A +DB_PASSWORD | changeme | `No` | N/A +DB_DATABASE | gearguardian | Yes | N/A +SECRET_KEY | changeme | `No` | N/A +ALGORITHM | HS256 | Yes | N/A +ACCESS_TOKEN_EXPIRE_MINUTES | 30 | Yes | N/A +STRAVA_CLIENT_ID | changeme | `No` | N/A +STRAVA_CLIENT_SECRET | changeme | `No` | N/A +STRAVA_AUTH_CODE | changeme | `No` | N/A +JAEGER_ENABLED | true | Yes | N/A +JAEGER_PROTOCOL | http | Yes | N/A +JAEGER_HOST | jaeger | Yes | N/A +JAGGER_PORT | 4317 | Yes | N/A +STRAVA_DAYS_ACTIVITIES_ONLINK | 30 | Yes | N/A +FRONTEND_PROTOCOL | http | Yes | Needs to be set if you want to enable Strava integration. You may need to update this variable based on docker image spin up +FRONTEND_HOST | frontend | Yes | Needs to be set if you want to enable Strava integration. You may need to update this variable based on docker image spin up +FRONTEND_PORT | frontend | Yes | Needs to be set if you want to enable Strava integration. You may need to update this variable based on docker image spin up +GEOCODES_MAPS_API | changeme | `No` | Geocode maps offers a free plan consisting of 1 Request/Second. Registration necessary. Table bellow shows the obligatory environemnt variables for mariadb container. You should set them based on what was also set for backend container. -Environemnt variable | Default value | Optional ---- | --- | --- -MYSQL_ROOT_PASSWORD | changeme | `No` -MYSQL_DATABASE | gearguardian | `No` -MYSQL_USER | gearguardian | `No` -MYSQL_PASSWORD | changeme | `No` +Environemnt variable | Default value | Optional | Notes +--- | --- | --- | --- +MYSQL_ROOT_PASSWORD | changeme | `No` | N/A +MYSQL_DATABASE | gearguardian | `No` | N/A +MYSQL_USER | gearguardian | `No` | N/A +MYSQL_PASSWORD | changeme | `No` | N/A Python backend dependencies used: - - python:3.11 - - fastapi==0.108.0 - - pydantic==1.10.9 - - uvicorn==0.25.0 - - python-dotenv==1.0.0 - - sqlalchemy==2.0.25 - - mysqlclient==2.2.1 + - fastapi==0.111.0 + - pydantic==1.10.15 + - uvicorn==0.29.0 + - python-dotenv==1.0.1 + - sqlalchemy==2.0.30 + - mysqlclient==2.2.4 - python-jose[cryptography]==3.3.0 - passlib[bcrypt]==1.7.4 - apscheduler==3.10.4 - - requests==2.31.0 - - stravalib==1.5 + - requests==2.32.2 + - stravalib==1.7 - opentelemetry-sdk==1.22.0 - opentelemetry-instrumentation-fastapi==0.43b0 - opentelemetry.exporter.otlp==1.22.0 - - python-multipart==0.0.6 + - python-multipart==0.0.9 - gpxpy==1.6.2 - alembic==1.13.1 diff --git a/backend/.env b/backend/.env deleted file mode 100644 index c2aaa5136..000000000 --- a/backend/.env +++ /dev/null @@ -1,19 +0,0 @@ -# .env -DB_HOST=mariadb -DB_PORT=3306 -DB_USER=endurain -DB_PASSWORD=changeme -DB_DATABASE=endurain -SECRET_KEY=changeme # openssl rand -hex 32 -ALGORITHM=HS256 -ACCESS_TOKEN_EXPIRE_MINUTES=30 -STRAVA_CLIENT_ID=changeme -STRAVA_CLIENT_SECRET=changeme -STRAVA_AUTH_CODE=changeme -JAEGER_ENABLED=true -JAEGER_PROTOCOL=http -JAEGER_HOST=jaeger -JAGGER_PORT=4317 -STRAVA_DAYS_ACTIVITIES_ONLINK=30 -FRONTEND_HOST=frontend -GEOCODES_MAPS_API=changeme \ No newline at end of file diff --git a/backend/alembic/versions/0ab200a7f196_remove_access_tokens_table.py b/backend/alembic/versions/0ab200a7f196_remove_access_tokens_table.py new file mode 100644 index 000000000..12845a3c7 --- /dev/null +++ b/backend/alembic/versions/0ab200a7f196_remove_access_tokens_table.py @@ -0,0 +1,59 @@ +"""Remove access_tokens table + +Revision ID: 0ab200a7f196 +Revises: 5fd61bc55e09 +Create Date: 2024-05-24 13:39:50.917676 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision: str = '0ab200a7f196' +down_revision: Union[str, None] = '5fd61bc55e09' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + # Drop the foreign key constraint first + op.drop_constraint('access_tokens_ibfk_1', 'access_tokens', type_='foreignkey') + # Then drop the index + op.drop_index('ix_access_tokens_user_id', table_name='access_tokens') + op.drop_table('access_tokens') + op.alter_column('users_integrations', 'user_id', + existing_type=mysql.INTEGER(display_width=11), + comment='User ID that the integration belongs', + existing_comment='User ID that the token belongs', + existing_nullable=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('users_integrations', 'user_id', + existing_type=mysql.INTEGER(display_width=11), + comment='User ID that the token belongs', + existing_comment='User ID that the integration belongs', + existing_nullable=False) + op.create_table('access_tokens', + sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False), + sa.Column('token', mysql.VARCHAR(length=256), nullable=False, comment='User token'), + sa.Column('user_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False, comment='User ID that the token belongs'), + sa.Column('created_at', mysql.DATETIME(), nullable=False, comment='Token creation date (date)'), + sa.Column('expires_at', mysql.DATETIME(), nullable=False, comment='Token expiration date (date)'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='access_tokens_ibfk_1', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + mysql_collate='utf8mb4_general_ci', + mysql_default_charset='utf8mb4', + mysql_engine='InnoDB' + ) + # Recreate the index first + op.create_index('ix_access_tokens_user_id', 'access_tokens', ['user_id']) + # Then recreate the foreign key constraint + op.create_foreign_key('access_tokens_ibfk_1', 'access_tokens', 'users', ['user_id'], ['id']) + # ### end Alembic commands ### diff --git a/backend/alembic/versions/5fd61bc55e09_add_cascade_delete_to_followers_foreign_.py b/backend/alembic/versions/5fd61bc55e09_add_cascade_delete_to_followers_foreign_.py new file mode 100644 index 000000000..e52e39879 --- /dev/null +++ b/backend/alembic/versions/5fd61bc55e09_add_cascade_delete_to_followers_foreign_.py @@ -0,0 +1,36 @@ +"""Add cascade delete to followers foreign keys + +Revision ID: 5fd61bc55e09 +Revises: 1bce2bd27873 +Create Date: 2024-05-21 20:59:54.984566 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '5fd61bc55e09' +down_revision: Union[str, None] = '1bce2bd27873' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('followers_ibfk_2', 'followers', type_='foreignkey') + op.drop_constraint('followers_ibfk_1', 'followers', type_='foreignkey') + op.create_foreign_key(None, 'followers', 'users', ['following_id'], ['id'], ondelete='CASCADE') + op.create_foreign_key(None, 'followers', 'users', ['follower_id'], ['id'], ondelete='CASCADE') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'followers', type_='foreignkey') + op.drop_constraint(None, 'followers', type_='foreignkey') + op.create_foreign_key('followers_ibfk_1', 'followers', 'users', ['follower_id'], ['id']) + op.create_foreign_key('followers_ibfk_2', 'followers', 'users', ['following_id'], ['id']) + # ### end Alembic commands ### diff --git a/backend/constants.py b/backend/constants.py index 8c8b6e064..a790f5114 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -1,7 +1,7 @@ import os # Constant related to version -API_VERSION="v0.1.5" +API_VERSION="v0.2.0" # JWT Token constants JWT_ALGORITHM = os.environ.get("ALGORITHM") diff --git a/backend/crud/crud_access_tokens.py b/backend/crud/crud_access_tokens.py deleted file mode 100644 index 2bf11bb82..000000000 --- a/backend/crud/crud_access_tokens.py +++ /dev/null @@ -1,129 +0,0 @@ -import logging - -import models - -from datetime import datetime - -from fastapi import HTTPException, status -from sqlalchemy.orm import Session - -# Define a loggger created on main.py -logger = logging.getLogger("myLogger") - - -def get_acess_tokens_by_user_id(user_id: int, db: Session): - try: - access_tokens = ( - db.query(models.AccessToken) - .filter(models.AccessToken.user_id == user_id) - .all() - ) - if access_tokens is None: - # If the user was not found, return a 404 Not Found error - return None - - return access_tokens - except Exception as err: - # Log the exception - logger.error(f"Error in get_acess_tokens_by_user_id: {err}", exc_info=True) - # Raise an HTTPException with a 500 Internal Server Error status code - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Internal Server Error", - ) from err - - -def create_access_token(token, db: Session): - try: - # Create a new access token in the database - db_access_token = models.AccessToken( - token=token.token, - user_id=token.user_id, - created_at=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"), - expires_at=token.expires_at, - ) - - # Add the access token to the database and commit the transaction - db.add(db_access_token) - db.commit() - db.refresh(db_access_token) - - # return the access token - return db_access_token - except Exception as err: - # Handle database-related exceptions - db.rollback() # Rollback the transaction to maintain database consistency - - # Log the exception - logger.error(f"Error in create_access_token: {err}", exc_info=True) - - # Raise an HTTPException with a 500 Internal Server Error status code - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Internal Server Error", - ) from err - - -def delete_access_token(token: str, db: Session): - try: - # Delete the access token from the database - db_access_token = ( - db.query(models.AccessToken) - .filter(models.AccessToken.token == token) - .delete() - ) - - # Commit the transaction to the database - if db_access_token: - db.delete(db_access_token) - db.commit() - logger.info(f"{db_access_token} access tokens deleted from the database") - return db_access_token - else: - # If the access token was not found, return a 404 Not Found error - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Access token not found", - ) - except Exception as err: - # Handle database-related exceptions - db.rollback() # Rollback the transaction to maintain database consistency - - # Log the exception - logger.error(f"Error in delete_access_token: {err}", exc_info=True) - - # Raise an HTTPException with a 500 Internal Server Error status code - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Internal Server Error", - ) from err - - -def delete_access_tokens(expiration_time: str, db: Session): - try: - # Delete the access tokens from the database - db_access_tokens = ( - db.query(models.AccessToken) - .filter(models.AccessToken.created_at < expiration_time) - .delete() - ) - - # Commit the transaction to the database - if db_access_tokens: - db.commit() - return db_access_tokens - else: - # If no access tokens were found, return 0 - return 0 - except Exception as err: - # Handle database-related exceptions - db.rollback() # Rollback the transaction to maintain database consistency - - # Log the exception - logger.error(f"Error in delete_access_tokens: {err}", exc_info=True) - - # Raise an HTTPException with a 500 Internal Server Error status code - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Internal Server Error", - ) from err diff --git a/backend/crud/crud_activities.py b/backend/crud/crud_activities.py index aee6962b0..6bb27fea9 100644 --- a/backend/crud/crud_activities.py +++ b/backend/crud/crud_activities.py @@ -5,6 +5,7 @@ from fastapi import HTTPException, status from datetime import datetime from sqlalchemy import func, desc from sqlalchemy.orm import Session, joinedload +from urllib.parse import unquote import models from schemas import schema_activities @@ -404,6 +405,46 @@ def get_activity_by_strava_id_from_user_id( ) from err +def get_activities_if_contains_name( + name: str, user_id: int, db: Session +): + try: + # Define a search term + partial_name = unquote(name).replace("+", " ") + + # Get the activities from the database + activities = ( + db.query(models.Activity) + .filter( + models.Activity.user_id == user_id, + models.Activity.name.like(f"%{partial_name}%"), + ) + .order_by(desc(models.Activity.start_time)) + .all() + ) + + # Check if there are activities if not return None + if not activities: + return None + + # Iterate and format the dates + for activity in activities: + activity.start_time = activity.start_time.strftime("%Y-%m-%d %H:%M:%S") + activity.end_time = activity.end_time.strftime("%Y-%m-%d %H:%M:%S") + activity.created_at = activity.created_at.strftime("%Y-%m-%d %H:%M:%S") + + # Return the activities + return activities + except Exception as err: + # Log the exception + logger.error(f"Error in get_activities_if_contains_name: {err}", exc_info=True) + # Raise an HTTPException with a 500 Internal Server Error status code + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Internal Server Error", + ) from err + + def create_activity(activity: schema_activities.Activity, db: Session): try: # Create a new activity diff --git a/backend/crud/crud_users.py b/backend/crud/crud_users.py index 9bd55d57a..5ca6a8f57 100644 --- a/backend/crud/crud_users.py +++ b/backend/crud/crud_users.py @@ -1,3 +1,5 @@ +import os +import glob import logging from fastapi import HTTPException, status @@ -11,6 +13,25 @@ import models # Define a loggger created on main.py logger = logging.getLogger("myLogger") +def delete_user_photo_filesystem(user_id: int): + # Define the pattern to match files with the specified name regardless of the extension + folder = "user_images" + file = f"{user_id}.*" + + print(os.path.join(folder, file)) + + # Find all files matching the pattern + files_to_delete = glob.glob(os.path.join(folder, file)) + + print(f"Files to delete: {files_to_delete}") + + # Remove each file found + for file_path in files_to_delete: + print(f"Deleting: {file_path}") + if os.path.exists(file_path): + os.remove(file_path) + print(f"Deleted: {file_path}") + def format_user_birthdate(user): user.birthdate = user.birthdate.strftime("%Y-%m-%d") if user.birthdate else None @@ -334,6 +355,10 @@ def edit_user(user: schema_users.User, db: Session): # Commit the transaction db.commit() + + if db_user.photo_path is None: + # Delete the user photo in the filesystem + delete_user_photo_filesystem(db_user.id) except IntegrityError as integrity_error: # Rollback the transaction db.rollback() @@ -379,6 +404,33 @@ def edit_user_password(user_id: int, password: str, db: Session): status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Internal Server Error", ) from err + + +def edit_user_photo_path(user_id: int, photo_path: str, db: Session): + try: + # Get the user from the database + db_user = db.query(models.User).filter(models.User.id == user_id).first() + + # Update the user + db_user.photo_path = photo_path + + # Commit the transaction + db.commit() + + # Return the photo path + return photo_path + except Exception as err: + # Rollback the transaction + db.rollback() + + # Log the exception + logger.error(f"Error in edit_user_photo_path: {err}", exc_info=True) + + # Raise an HTTPException with a 500 Internal Server Error status code + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Internal Server Error", + ) from err def delete_user_photo(user_id: int, db: Session): @@ -392,6 +444,9 @@ def delete_user_photo(user_id: int, db: Session): # Commit the transaction db.commit() + + # Delete the user photo in the filesystem + delete_user_photo_filesystem(user_id) except Exception as err: # Rollback the transaction db.rollback() @@ -420,6 +475,9 @@ def delete_user(user_id: int, db: Session): # Commit the transaction db.commit() + + # Delete the user photo in the filesystem + delete_user_photo_filesystem(user_id) except Exception as err: # Rollback the transaction db.rollback() diff --git a/backend/main.py b/backend/main.py index f6f5bae01..b00b8ad8c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2,6 +2,8 @@ import logging import os from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles from alembic.config import Config from alembic import command @@ -37,15 +39,13 @@ def startup_event(): # Run Alembic migrations to ensure the database is up to date alembic_cfg = Config("alembic.ini") # Disable the logger configuration in Alembic to avoid conflicts with FastAPI - alembic_cfg.attributes['configure_logger'] = False + alembic_cfg.attributes["configure_logger"] = False command.upgrade(alembic_cfg, "head") # Create a scheduler to run background jobs scheduler.start() - # Job to remove expired tokens every 5 minutes - logger.info("Added scheduler job to remove expired tokens every 5 minutes") - scheduler.add_job(remove_expired_tokens_job, "interval", minutes=5) + # Add scheduler jobs to refresh Strava tokens and retrieve last day activities logger.info("Added scheduler job to refresh Strava user tokens every 60 minutes") scheduler.add_job(refresh_strava_tokens_job, "interval", minutes=60) logger.info( @@ -64,17 +64,6 @@ def shutdown_event(): scheduler.shutdown() -def remove_expired_tokens_job(): - # Create a new database session - db = SessionLocal() - try: - # Remove expired tokens from the database - schema_access_tokens.remove_expired_tokens(db=db) - finally: - # Ensure the session is closed after use - db.close() - - def refresh_strava_tokens_job(): # Create a new database session db = SessionLocal() @@ -121,7 +110,9 @@ required_env_vars = [ "JAEGER_HOST", "JAGGER_PORT", "STRAVA_DAYS_ACTIVITIES_ONLINK", + "FRONTEND_PROTOCOL", "FRONTEND_HOST", + "FRONTEND_PORT", "GEOCODES_MAPS_API", ] @@ -147,6 +138,29 @@ app = FastAPI( }, ) +# Add a route to serve the user images +app.mount("/user_images", StaticFiles(directory="user_images"), name="user_images") + +# Add CORS middleware to allow requests from the frontend +origins = [ + "http://localhost", + "http://localhost:8080", + "http://localhost:5173", + os.environ.get("FRONTEND_PROTOCOL") + + "://" + + os.environ.get("FRONTEND_HOST") + + ":" + + os.environ.get("FRONTEND_PORT"), +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + # Router files app.include_router(router_session.router) app.include_router(router_users.router) diff --git a/backend/models.py b/backend/models.py index 8a50696c7..5e17c461e 100644 --- a/backend/models.py +++ b/backend/models.py @@ -20,14 +20,14 @@ class Follower(Base): follower_id = Column( Integer, - ForeignKey("users.id"), + ForeignKey("users.id", ondelete="CASCADE"), primary_key=True, index=True, comment="ID of the follower user", ) following_id = Column( Integer, - ForeignKey("users.id"), + ForeignKey("users.id", ondelete="CASCADE"), primary_key=True, index=True, comment="ID of the following user", @@ -103,12 +103,6 @@ class User(Base): back_populates="user", cascade="all, delete-orphan", ) - # Define a relationship to AccessToken model - access_tokens = relationship( - "AccessToken", - back_populates="user", - cascade="all, delete-orphan", - ) # Define a relationship to Gear model gear = relationship( "Gear", @@ -147,7 +141,7 @@ class UserIntegrations(Base): ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, - comment="User ID that the token belongs", + comment="User ID that the integration belongs", ) strava_state = Column(String(length=45), default=None, nullable=True) strava_token = Column(String(length=250), default=None, nullable=True) @@ -164,28 +158,6 @@ class UserIntegrations(Base): user = relationship("User", back_populates="users_integrations") -# Data model for access_tokens table using SQLAlchemy's ORM -class AccessToken(Base): - __tablename__ = "access_tokens" - - id = Column(Integer, primary_key=True) - token = Column(String(length=256), nullable=False, comment="User token") - user_id = Column( - Integer, - ForeignKey("users.id", ondelete="CASCADE"), - nullable=False, - index=True, - comment="User ID that the token belongs", - ) - created_at = Column(DateTime, nullable=False, comment="Token creation date (date)") - expires_at = Column( - DateTime, nullable=False, comment="Token expiration date (date)" - ) - - # Define a relationship to the User model - user = relationship("User", back_populates="access_tokens") - - # Data model for gear table using SQLAlchemy's ORM class Gear(Base): __tablename__ = "gear" diff --git a/backend/routers/router_activities.py b/backend/routers/router_activities.py index f2d2a9de0..c2cf0ef52 100644 --- a/backend/routers/router_activities.py +++ b/backend/routers/router_activities.py @@ -332,17 +332,35 @@ async def read_activities_activity_from_id( return crud_activities.get_activity_by_id_from_user_id_or_has_visibility(activity_id, token_user_id, db) +@router.get( + "/activities/name/contains/{name}", + response_model=list[schema_activities.Activity] | None, + tags=["activities"], +) +async def read_activities_contain_name( + name: str, + token_user_id: Annotated[ + Callable, + Depends(dependencies_session.validate_token_and_get_authenticated_user_id), + ], + db: Session = Depends(dependencies_database.get_db), +): + # Get the activities from the database by name + return crud_activities.get_activities_if_contains_name(name, token_user_id, db) + + @router.post( - "/activities/{user_id}/create/upload", + "/activities/create/upload", status_code=201, response_model=int, tags=["activities"], ) async def create_activity_with_uploaded_file( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + token_user_id: Annotated[ + Callable, + Depends(dependencies_session.validate_token_and_get_authenticated_user_id), + ], file: UploadFile, - validate_token: Annotated[Callable, Depends(dependencies_session.validate_token)], db: Session = Depends(dependencies_database.get_db), ): try: @@ -360,10 +378,10 @@ async def create_activity_with_uploaded_file( # Choose the appropriate parser based on file extension if file_extension.lower() == ".gpx": # Parse the GPX file - parsed_info = gpx_processor.parse_gpx_file(file.filename, user_id) + parsed_info = gpx_processor.parse_gpx_file(file.filename, token_user_id) elif file_extension.lower() == ".fit": # Parse the FIT file - parsed_info = fit_processor.parse_fit_file(file.filename, user_id) + parsed_info = fit_processor.parse_fit_file(file.filename, token_user_id) else: # file extension not supported raise an HTTPException with a 406 Not Acceptable status code raise HTTPException( diff --git a/backend/routers/router_strava.py b/backend/routers/router_strava.py index 826d1bd51..27577b220 100644 --- a/backend/routers/router_strava.py +++ b/backend/routers/router_strava.py @@ -80,7 +80,7 @@ async def strava_link( redirect_url = ( "https://" + os.environ.get("FRONTEND_HOST") - + "/settings/settings.php?integrationsSettings=1&stravaLinked=1" + + "/settings?stravaLinked=1" ) # Return a RedirectResponse to the redirect URL diff --git a/backend/routers/router_users.py b/backend/routers/router_users.py index 8d3dd52f2..d6c43e633 100644 --- a/backend/routers/router_users.py +++ b/backend/routers/router_users.py @@ -1,11 +1,14 @@ +import os import logging from typing import Annotated, Callable -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException, status, UploadFile from fastapi.security import OAuth2PasswordBearer from sqlalchemy.orm import Session +import shutil + from schemas import schema_users from crud import crud_user_integrations, crud_users from dependencies import ( @@ -158,6 +161,52 @@ async def create_user( return created_user.id +@router.post( + "/users/{user_id}/upload/image", + status_code=201, + response_model=str | None, + tags=["users"], +) +async def upload_user_image( + user_id: int, + token_user_id: Annotated[ + Callable, + Depends(dependencies_session.validate_token_and_get_authenticated_user_id), + ], + file: UploadFile, + db: Session = Depends(dependencies_database.get_db), +): + try: + upload_dir = "user_images" + os.makedirs(upload_dir, exist_ok=True) + + # Get file extension + _, file_extension = os.path.splitext(file.filename) + filename = f"{user_id}{file_extension}" + + file_path_to_save = os.path.join(upload_dir, filename) + + with open(file_path_to_save, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + return crud_users.edit_user_photo_path(user_id, file_path_to_save, db) + except Exception as err: + # Log the exception + logger.error( + f"Error in upload_user_image: {err}", exc_info=True + ) + + # Remove the file after processing + if os.path.exists(file_path_to_save): + os.remove(file_path_to_save) + + # Raise an HTTPException with a 500 Internal Server Error status code + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Internal Server Error", + ) from err + + @router.put("/users/edit", tags=["users"]) async def edit_user( user_attributtes: schema_users.User, diff --git a/backend/schemas/schema_access_tokens.py b/backend/schemas/schema_access_tokens.py index aade3905c..703632f9f 100644 --- a/backend/schemas/schema_access_tokens.py +++ b/backend/schemas/schema_access_tokens.py @@ -7,7 +7,6 @@ from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from sqlalchemy.orm import Session -from crud import crud_access_tokens from constants import ( JWT_EXPIRATION_IN_MINUTES, JWT_ALGORITHM, @@ -69,9 +68,10 @@ def validate_token_expiration(db: Session, token: str = Depends(oauth2_scheme)): or datetime.utcfromtimestamp(expiration_timestamp) < datetime.utcnow() ): logger.warning( - "Token expired | Will force remove_expired_tokens to run | Returning 401 response" + "Token expired | Returning 401 response" ) - remove_expired_tokens(db) + + # Raise an HTTPException with a 401 Unauthorized status code raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token no longer valid", @@ -80,10 +80,9 @@ def validate_token_expiration(db: Session, token: str = Depends(oauth2_scheme)): except Exception: # Log the error and raise the exception logger.info( - "Token expired during validation | Will force remove_expired_tokens to run | Returning 401 response" + "Token expired during validation | Returning 401 response" ) - # Remove expired tokens from the database - remove_expired_tokens(db) + # Raise an HTTPException with a 401 Unauthorized status code raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -159,29 +158,5 @@ def create_access_token( # Encode the data and return the token encoded_jwt = jwt.encode(to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM) - # Save the token in the database - db_access_token = crud_access_tokens.create_access_token( - CreateToken( - token=encoded_jwt, - user_id=data.get("id"), - expires_at=expire.strftime("%Y-%m-%dT%H:%M:%S"), - ), - db, - ) - if db_access_token: - # Return the token - return encoded_jwt - else: - # If the token could not be saved in the database return None - return None - - -def remove_expired_tokens(db: Session): - # Calculate the expiration time - expiration_time = datetime.utcnow() - timedelta(minutes=JWT_EXPIRATION_IN_MINUTES) - - # Delete the expired tokens from the database - rows_deleted = crud_access_tokens.delete_access_tokens(expiration_time, db) - - # Log the number of tokens deleted - logger.info(f"{rows_deleted} access tokens deleted from the database") + # Return the token + return encoded_jwt diff --git a/backend/user_images/__init__.py b/backend/user_images/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend_otel/otel-collector-config.yaml b/backend_otel/otel-collector-config.yaml deleted file mode 100644 index 863f970d6..000000000 --- a/backend_otel/otel-collector-config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -receivers: - otlp: - protocols: - http: - -processors: - batch: - -exporters: - jaeger: - endpoint: "jaeger:14268" # Use the service name defined in your Jaeger service - -service: - pipelines: - traces: - receivers: [otlp] - processors: [batch] - exporters: [jaeger] \ No newline at end of file diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..e79638a12 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,7 @@ +files: + - source: /frontend_vue/src/i18n/en/*.json + translation: /frontend_vue/src/i18n/%two_letters_code%/%file_name%.%file_extension% + - source: /frontend_vue/src/i18n/en/components/*.json + translation: /frontend_vue/src/i18n/%two_letters_code%/components/%file_name%.%file_extension% + - source: /frontend_vue/src/i18n/en/gears/*.json + translation: /frontend_vue/src/i18n/%two_letters_code%/gears/%file_name%.%file_extension% diff --git a/docker-compose.yml b/docker-compose.yml index 9639e93be..9f07b0b7a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,16 +4,14 @@ services: frontend: container_name: frontend image: ghcr.io/joaovitoriasilva/endurain/frontend:latest - environment: - - BACKEND_PROTOCOL=https # http or https, default is http - - BACKEND_HOST=backend # api host, default is backend + #environment: + #- MY_APP_BACKEND_PROTOCOL=http # http or https, default is http + #- MY_APP_BACKEND_HOST=localhost:98 # api host or local ip (example: 192.168.1.10:98), default is localhost:98 # Configure volume if you want to edit the code locally by clomming the repo #volumes: - # - /endurain/frontend:/var/www/html + # - /endurain/frontend:/app ports: - "8080:80" # frontend port, change per your needs - env_file: - - .env restart: unless-stopped # API logic @@ -26,14 +24,15 @@ services: - STRAVA_CLIENT_ID=changeme - STRAVA_CLIENT_SECRET=changeme - STRAVA_AUTH_CODE=changeme - - FRONTEND_HOST=frontend # default is frontend + - GEOCODES_MAPS_API=changeme + - FRONTEND_PROTOCOL=http # default is http + - FRONTEND_HOST=frontend # frontend host or local ip (example: 192.168.1.10:8080), default is frontend + - FRONTEND_PORT=8080 # default is 80 ports: - "98:80" # API port, change per your needs # Configure volume if you want to edit the code locally by clomming the repo #volumes: # - /endurain/backend:/app - env_file: - - .env depends_on: - mariadb - jaeger diff --git a/frontend/.env b/frontend/.env old mode 100755 new mode 100644 index e211d5c85..f9fd581a5 --- a/frontend/.env +++ b/frontend/.env @@ -1,3 +1,2 @@ -# .env -BACKEND_PROTOCOL=http -BACKEND_HOST=backend \ No newline at end of file +VITE_BACKEND_PROTOCOL=MY_APP_BACKEND_PROTOCOL +VITE_BACKEND_HOST=MY_APP_BACKEND_HOST \ No newline at end of file diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs new file mode 100644 index 000000000..b64731a0f --- /dev/null +++ b/frontend/.eslintrc.cjs @@ -0,0 +1,14 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + 'extends': [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-prettier/skip-formatting' + ], + parserOptions: { + ecmaVersion: 'latest' + } +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 000000000..4a1356a1a --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +# Sentry Config File +.env.sentry-build-plugin diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json new file mode 100644 index 000000000..66e23359c --- /dev/null +++ b/frontend/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json new file mode 100644 index 000000000..009a53485 --- /dev/null +++ b/frontend/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "Vue.volar", + "Vue.vscode-typescript-vue-plugin", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/frontend/activities/activity.php b/frontend/activities/activity.php deleted file mode 100755 index 560d90adb..000000000 --- a/frontend/activities/activity.php +++ /dev/null @@ -1,973 +0,0 @@ - - - - -
- - - - - - - - - - - - - -
- -
- alt="userPicture" class="rounded-circle" width="55" height="55"> -
-
- "> - - -
- - - '; - } else { - if ($activity["activity_type"] == 4 || $activity["activity_type"] == 5 || $activity["activity_type"] == 6 || $activity["activity_type"] == 7 || $activity["activity_type"] == 8) { - echo ''; - } else { - if ($activity["activity_type"] == 9) { - echo ''; - } - } - } ?> - format("d/m/y"); ?>@ - format("H:i"); ?> - - - - -
-
- -
- - - - - -

- -

- - -
-
- - - -
- - km - - m - -
-
- - - -
- diff($endDateTime); - - if ($interval->h < 1) { - // If the difference is less than one hour - echo $interval->i . "m " . $interval->s . "s"; - } else { - // If the difference is one hour or more - echo $interval->h . "h " . $interval->i . "m"; - } - ?> -
-
- - - - -
- m - - - - - -
- - min/km - - - - - -
- - min/100m - - - -
-
- -
- -
- - - -
- W - -
-
- - - -
- m -
-
- - - -
- m -
- - -
- - - -
- km/h -
-
- - - -
- W - -
-
- - - -
- m -
- -
- - - -
- - - - - - - - -
-
-

- - - -
- '; - } else { - if ($activity["activity_type"] == 4 || $activity["activity_type"] == 5 || $activity["activity_type"] == 6 || $activity["activity_type"] == 7 || $activity["activity_type"] == 8) { - echo ''; - } else { - if ($activity["activity_type"] == 9) { - echo ''; - } - } - } ?> - - - - - -

-
- - - - - - - - - - - - - - - - - - - -
-
- - -
-
- - - -
-

- -

-
- - -
-
- - -
-
- - -
-
- - -
- -
- - -
- -
- - -
- -
-
- -

- -

-
-
- - - - - -
-
- -
- -
- - \ No newline at end of file diff --git a/frontend/gear/gear.php b/frontend/gear/gear.php deleted file mode 100755 index 6e1824960..000000000 --- a/frontend/gear/gear.php +++ /dev/null @@ -1,390 +0,0 @@ - - - - -
-

- -

-
- -
- - - - - - - - - - -
- -
- -
- alt="gearPicture" width="180" height="180"> - -
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - " href="#" role="button" data-bs-toggle="modal" - data-bs-target="#editGearModal"> - - - - - - - - " href="#" role="button" data-bs-toggle="modal" - data-bs-target="#deleteGearModal" > - - - - - -
- - : - - km - - - - : - - - - - - - : - - - - -
-
-
- -
-
- - - -
-
-
- -
- - - -
- - - -
    - - -
  • - "> - - - - : - - format("d/m/y"); ?>@ - format("H:i"); ?> - -
  • - - -
- -
-
-
- -
-
- -
- -
- - \ No newline at end of file diff --git a/frontend/gear/gears.php b/frontend/gear/gears.php deleted file mode 100755 index 0342231fc..000000000 --- a/frontend/gear/gears.php +++ /dev/null @@ -1,309 +0,0 @@ - - - - -
-

-
- -
- - - - - - - - - - - - - - - -
-
- -

- - - - - -
-

-
-
- - -
- -
- -
- - -
-
- - - - - - - -

( :

- - -
- - - - -
-
-
-
- -
- -
- - \ No newline at end of file diff --git a/frontend/img/strava/api_logo_cptblWith_strava_stack_light.svg b/frontend/img/strava/api_logo_cptblWith_strava_stack_light.svg deleted file mode 100644 index 71dd3f71d..000000000 --- a/frontend/img/strava/api_logo_cptblWith_strava_stack_light.svg +++ /dev/null @@ -1 +0,0 @@ -api_logo \ No newline at end of file diff --git a/frontend/img/strava/btn_strava_connectwith_orange.png b/frontend/img/strava/btn_strava_connectwith_orange.png deleted file mode 100755 index f8132f134..000000000 Binary files a/frontend/img/strava/btn_strava_connectwith_orange.png and /dev/null differ diff --git a/frontend/inc/Template-Bottom.php b/frontend/inc/Template-Bottom.php deleted file mode 100755 index 9dd42f095..000000000 --- a/frontend/inc/Template-Bottom.php +++ /dev/null @@ -1,15 +0,0 @@ - -
-

© - 2023 - - Endurain • • v0.1.5 -

-

Compatible with STRAVA image

-
- - \ No newline at end of file diff --git a/frontend/inc/Template-Top.php b/frontend/inc/Template-Top.php deleted file mode 100755 index 34caa9883..000000000 --- a/frontend/inc/Template-Top.php +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - Endurain - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/inc/func/activities-funcs.php b/frontend/inc/func/activities-funcs.php deleted file mode 100755 index 30543c13c..000000000 --- a/frontend/inc/func/activities-funcs.php +++ /dev/null @@ -1,273 +0,0 @@ - $activity_id, - 'stream_type' => $stream_type, - 'stream_waypoints' => $stream_waypoints, - 'strava_activity_stream_id' => $strava_activity_stream_id, - ))); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 201) { - return 0; - } else { - return -2; - } - } -} \ No newline at end of file diff --git a/frontend/inc/func/followers-funcs.php b/frontend/inc/func/followers-funcs.php deleted file mode 100755 index 5848dd555..000000000 --- a/frontend/inc/func/followers-funcs.php +++ /dev/null @@ -1,152 +0,0 @@ - $brand, - 'model' => $model, - 'nickname' => $nickname, - 'gear_type' => $gear_type, - 'created_at' => $date, - ))); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 201) { - return 0; - } else { - return -2; - } - } -} - -/* Edit gear */ -function editGear($id, $brand, $model, $nickname, $gear_type, $date, $is_active) -{ - $response = callAPIRoute("/gear/$id/edit", 0, 3, json_encode(array( - 'brand' => $brand, - 'model' => $model, - 'nickname' => $nickname, - 'gear_type' => $gear_type, - 'created_at' => $date, - 'is_active' => $is_active, - ))); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 200) { - return 0; - } else { - return -2; - } - } -} - -/* Deletes a gear based on its ID */ -function deleteGear($id) -{ - $response = callAPIRoute("/gear/$id/delete", 1, 1, NULL); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 200) { - return 0; - } else { - return -2; - } - } -} diff --git a/frontend/inc/func/main-api-funcs.php b/frontend/inc/func/main-api-funcs.php deleted file mode 100755 index 27b3843f2..000000000 --- a/frontend/inc/func/main-api-funcs.php +++ /dev/null @@ -1,117 +0,0 @@ - new CURLFile($dataFields), - ]); - } - } - } - } - } - } - - if ($multipleReturns == 1) { - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - "Authorization: Bearer {$_SESSION["token"]}" - )); - } else { - if ($callType == 4 || $callType == 3) { - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - "Content-Type: application/json", - "Authorization: Bearer {$_SESSION["token"]}" - )); - } else { - if ($callType == 5) { - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - "Content-Type: application/json" - )); - } else { - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - "Content-Type: application/x-www-form-urlencoded" - )); - } - } - } - - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - // Execute the cURL request and store the response in a variable - $response = curl_exec($ch); - - $responseArray[] = $response; - $responseArray[] = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - curl_close($ch); - - return $responseArray; -} - -function parseResponse($response, $success_code) -{ - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === $success_code) { - return json_decode($response[0], true); - } else { - if ($response[1] === 401) { - if (json_decode($response[0], true)["detail"] === "Token no longer valid"){ - #clearUserRelatedInfoSession(); - #header("location: ../logout.php?sessionExpired=1"); - } - }else{ - if ($response[1] === 403) { - return -2; - }else{ - if ($response[1] === 409) { - #return { - # "error_code" => $response[1], - # "error_message" => json_decode($response[0], true)["detail"], - #}; - }else{ - return -2; - } - } - } - } - } -} \ No newline at end of file diff --git a/frontend/inc/func/session-funcs.php b/frontend/inc/func/session-funcs.php deleted file mode 100755 index cd89f984d..000000000 --- a/frontend/inc/func/session-funcs.php +++ /dev/null @@ -1,98 +0,0 @@ -= 0) { - return TRUE; - } - } - return FALSE; -} - -/* Check if user token is valid */ -function isTokenValid($token) -{ - $response = callAPIRoute("/validate_token", 1, 0, NULL); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 200) { - return true; - } else { - return false; - } - } -} - -/* Do a login */ -function loginUser($username, $password, $neverExpires) -{ - $response = callAPIRoute("/token", 0, 2, array( - 'username' => $username, - 'password' => $password, - 'do_not_expire' => $neverExpires, - )); - return $response; -} - - -/* Unset user info */ -function clearUserRelatedInfoSession() -{ - unset($_SESSION["token"]); - unset($_SESSION["id"]); - unset($_SESSION["username"]); - unset($_SESSION["email"]); - unset($_SESSION["name"]); - unset($_SESSION["city"]); - unset($_SESSION["birthdate"]); - unset($_SESSION["preferred_language"]); - unset($_SESSION["gender"]); - unset($_SESSION["access_type"]); - unset($_SESSION["photo_path"]); - unset($_SESSION["photo_path_aux"]); -} - -/* Set user info */ -function setUserRelatedInfoSession($token) -{ - clearUserRelatedInfoSession(); - - $_SESSION["token"] = $token; - - $response = callAPIRoute("/users/me", 1, 0, NULL); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 200) { - $user = json_decode($response[0], true); - if ($user['is_active'] == 0) { - clearUserRelatedInfoSession(); - return -3; - } - // Populate the $_SESSION variable with user information - // $_SESSION["token"] = $token; - $_SESSION["id"] = $user['id']; - $_SESSION["name"] = $user['name']; - $_SESSION["username"] = $user['username']; - $_SESSION["email"] = $user['email']; - $_SESSION["city"] = $user['city']; - #$_SESSION["birthdate"] = date("d/m/Y", strtotime($user['birthdate'])); - $_SESSION["birthdate"] = $user['birthdate']; - $_SESSION["preferred_language"] = $user['preferred_language']; - $_SESSION["gender"] = $user['gender']; - $_SESSION["access_type"] = $user['access_type']; - $_SESSION["photo_path"] = $user['photo_path']; - $_SESSION["photo_path_aux"] = $user['photo_path_aux']; - $_SESSION["is_strava_linked"] = $user['is_strava_linked']; - return 0; - } else { - return -2; - } - } -} diff --git a/frontend/inc/func/strava-funcs.php b/frontend/inc/func/strava-funcs.php deleted file mode 100755 index 1e18818ed..000000000 --- a/frontend/inc/func/strava-funcs.php +++ /dev/null @@ -1,75 +0,0 @@ -location.href = '$strava_auth_url';"; - # -} - -function getStravaActivitiesLastDays($days) -{ - $response = callAPIRoute("/strava/activities/days/$days", 1, 0, NULL); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 202) { - return 0; - } else { - return -2; - } - } -} - -function getStravaGear() -{ - $response = callAPIRoute("/strava/gear", 1, 0, NULL); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 202) { - return 0; - } else { - return -2; - } - } -} \ No newline at end of file diff --git a/frontend/inc/func/users-funcs.php b/frontend/inc/func/users-funcs.php deleted file mode 100755 index 8b33aa9e7..000000000 --- a/frontend/inc/func/users-funcs.php +++ /dev/null @@ -1,233 +0,0 @@ - $name, - 'username' => $username, - 'email' => $email, - 'password' => hash("sha256", $password), - 'preferred_language' => $preferred_language, - 'city' => $city, - 'birthdate' => $birthdate, - 'gender' => $gender, - 'access_type' => $access_type, - 'photo_path' => $photo_path, - 'photo_path_aux' => $photo_path_aux, - 'is_active' => $is_active, - ))); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 201) { - return 0; - } else { - return -2; - } - } -} - -/* Edit user */ -function editUser($name, $username, $email, $id, $preferred_language, $city, $birthdate, $gender, $access_type, $photo_path, $photo_path_aux, $is_active) -{ - $response = callAPIRoute("/users/edit", 0, 3, json_encode(array( - 'id' => $id, - 'name' => $name, - 'username' => $username, - 'email' => $email, - 'preferred_language' => $preferred_language, - 'city' => $city, - 'birthdate' => $birthdate, - 'gender' => $gender, - 'access_type' => $access_type, - 'photo_path' => $photo_path, - 'photo_path_aux' => $photo_path_aux, - 'is_active' => $is_active, - ))); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 200) { - return 0; - } else { - return -2; - } - } -} - -/* Edit user password */ -function editUserPassword($user_id, $password){ - $response = callAPIRoute("/users/edit/password", 0, 3, json_encode(array( - 'id' => $user_id, - 'password' => hash("sha256", $password), - ))); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 200) { - return 0; - } else { - return -2; - } - } -} - -/* Unset user photo */ -function unsetUserPhoto($id) -{ - $response = callAPIRoute("/users/$id/delete-photo", 0, 3, NULL); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 200) { - return 0; - } else { - return -2; - } - } -} - -/* Deletes a user based on its ID */ -function deleteUser($id) -{ - $response = callAPIRoute("/users/$id/delete", 1, 1, NULL); - if ($response[0] === false) { - return -1; - } else { - if ($response[1] === 200) { - return 0; - } else { - if ($response[1] === 409) { - return -409; - } else { - return -2; - } - } - } -} diff --git a/frontend/inc/sqlFunctions.php b/frontend/inc/sqlFunctions.php deleted file mode 100755 index a0131ff74..000000000 --- a/frontend/inc/sqlFunctions.php +++ /dev/null @@ -1,9 +0,0 @@ - + + + + + + + Endurain + + +
+ + + + \ No newline at end of file diff --git a/frontend/index.php b/frontend/index.php deleted file mode 100755 index 5fdb8ec58..000000000 --- a/frontend/index.php +++ /dev/null @@ -1,802 +0,0 @@ - - - - -
- -
- - -
-
- -
-
-
- alt="userPicture" class="rounded-circle" width="120" height="120"> -
-
- "> - - -
- - - - - -
-
- - - -
- - km - - 0 km - -
-
- - - -
- - km - - 0 km - -
-
- - - -
- - 10000) { ?> - km - - m - - - 0 m - -
-
- - - - -
-
- - - -
- - km - - 0 km - -
-
- - - -
- - km - - 0 km - -
-
- - - -
- - 10000) { ?> - km - - m - - - 0 m - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - - -
- - - - - -
- -
- - - - -
-
-
-
- alt="userPicture" class="rounded-circle" width="55" height="55"> -
- - - '; - } else { - if ($activity["activity_type"] == 3) { - echo ' (Virtual)'; - } else { - if ($activity["activity_type"] == 4 || $activity["activity_type"] == 5 || $activity["activity_type"] == 6) { - echo ''; - } else { - if ($activity["activity_type"] == 7) { - echo ' (Virtual)'; - } else { - if ($activity["activity_type"] == 8 || $activity["activity_type"] == 9) { - echo ''; - } else { - if ($activity["activity_type"] == 10) { - echo ''; - } - } - } - } - } - } ?> - format("d/m/y"); ?>@ - format("H:i"); ?> - - - - -
-
- -
-
-
- - - -
- - km - - m - -
-
- - - -
- diff($endDateTime); - - if ($interval->h < 1) { - // If the difference is less than one hour - echo $interval->i . "m " . $interval->s . "s"; - } else { - // If the difference is one hour or more - echo $interval->h . "h " . $interval->i . "m"; - } - ?> -
-
- - - - -
- m - - - - - -
- - min/km - - - - - -
- - min/km - - - -
-
-
- -
" id="map_" style="height: 300px">
- - -
-
- - - - -
- -
- - - - - -
-
- -
- - - - \ No newline at end of file diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json new file mode 100644 index 000000000..5a1f2d222 --- /dev/null +++ b/frontend/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/frontend/lang/activities/en.php b/frontend/lang/activities/en.php deleted file mode 100755 index 8ade81bf0..000000000 --- a/frontend/lang/activities/en.php +++ /dev/null @@ -1,49 +0,0 @@ - "Activity", - // error banner zone - "activity_addEditGear_API_error_-1" => "Connection to API not possible. Failed to execute cURL request", - "activity_addEditGear_API_error_-2" => "API return code was not 200", - // success banner zone - "activity_success_gearAdded" => "Gear added to activity", - "activity_success_gearEdited" => "Activity gear edited", - "activity_success_gearDeleted" => "Activity gear deleted", - // activity title - "activity_title_dropdown_seeItOnStrava" => "See it on Strava", - "activity_title_dropdown_deleteActivity" => "Delete Activity", - "activity_title_dropdown_deleteActivity_modal_body" => "Are you sure you want to delete activity", - // activity details - "activity_detail_distance" => "Distance", - "activity_detail_time" => "Time", - "activity_detail_elevationGain" => "Elevation gain", - "activity_detail_elevationLoss" => "Elevation loss", - "activity_detail_pace" => "pace", - "activity_detail_avgSpeed" => "Avg speed", - "activity_detail_avgPower" => "Avg power", - // other gear - "activity_gear_title" => "Gear", - "activity_gear_notset" => "Gear not set for this activity", - // add gear - "activity_gear_addGear_title" => "Add gear to activity", - "activity_gear_addGear_label" => "Select gear", - "activity_gear_addGear_submit" => "Add gear", - // edit gear - "activity_gear_editGear_title" => "Edit activity gear", - "activity_gear_editGear_submit" => "Edit gear", - // delete gear zone - "activity_gear_deleteGear_title" => "Delete activity gear", - "activity_gear_deleteGear_body" => "Are you sure you want to delete activity gear", - // data graph - "activity_dataGraph_dataSelection" => "Data selection", - "activity_dataGraph_hr" => "Heart Rate", - "activity_dataGraph_cad" => "Cadence", - "activity_dataGraph_power" => "Power", - "activity_dataGraph_ele" => "Elevation", - "activity_dataGraph_vel" => "Velocity", - "activity_dataGraph_pace" => "Pace", - "activity_dataGraph_title" => "Data graph", - "activity_dataGraph_downsampleDataInfo" => "Downsample data (200 points)" -]; diff --git a/frontend/lang/gear/gear/en.php b/frontend/lang/gear/gear/en.php deleted file mode 100755 index 19473bae3..000000000 --- a/frontend/lang/gear/gear/en.php +++ /dev/null @@ -1,46 +0,0 @@ - "Connection to API not possible. Failed to execute cURL request", - "gear_API_error_-2" => "API return code was not 200", - // success banner zone - "gear_success_gearEdited" => "Gear edited.", - // gear info zone - "gear_gear_infoZone_isactive" => "Active", - "gear_gear_infoZone_isinactive" => "Inactive", - "gear_gear_infoZone_gearisbike" => "Bike", - "gear_gear_infoZone_gearisshoe" => "Shoes", - "gear_gear_infoZone_geariswetsuit" => "Wetsuit", - "gear_gear_infoZone_strava_gear" => "Synced from Strava", - // edit zone - "gear_gear_infoZone_editbutton" => "Edit gear", - "gear_gear_infoZone_modal_editGear_brandLabel" => "Brand", - "gear_gear_infoZone_modal_editGear_brandPlaceholder" => "Brand (max 45 characters)", - "gear_gear_infoZone_modal_editGear_modelLabel" => "Model", - "gear_gear_infoZone_modal_editGear_modelPlaceholder" => "Model (max 45 characters)", - "gear_gear_infoZone_modal_editGear_nicknameLabel" => "Nickname", - "gear_gear_infoZone_modal_editGear_nicknamePlaceholder" => "Nickname (max 45 characters)", - "gear_gear_infoZone_modal_editGear_gearTypeLabel" => "Gear type", - "gear_gear_infoZone_modal_editGear_gearTypeOption1" => "Bike", - "gear_gear_infoZone_modal_editGear_gearTypeOption2" => "Shoes", - "gear_gear_infoZone_modal_editGear_gearTypeOption3" => "Wetsuit", - "gear_gear_infoZone_modal_editGear_dateLabel" => "Created date", - "gear_gear_infoZone_modal_editGear_datePlaceholder" => "Date", - "gear_gear_infoZone_modal_editGear_gearIsActiveLabel" => "Is active", - "gear_gear_infoZone_modal_editGear_gearIsActiveOption1" => "Yes", - "gear_gear_infoZone_modal_editGear_gearIsActiveOption2" => "No", - // delete zone - "gear_gear_infoZone_modal_deleteGear_body" => "Are you sure you want to delete gear", - "gear_gear_infoZone_deletebutton" => "Delete gear", - // details - "gear_gear_infoZone_distance" => "Distance", - "gear_gear_infoZone_brand" => "Brand", - "gear_gear_infoZone_model" => "Model", - // gear activities - "gear_gear_gearActivities_noactivities" => "Gear is not linked to any activity", - "gear_gear_gearActivities_title" => "Gear activities", - "gear_gear_gearActivities_number" => "(last 10 activities)", - "gear_gear_gearActivities_datelabel" => "Date" -]; diff --git a/frontend/lang/gear/gears/en.php b/frontend/lang/gear/gears/en.php deleted file mode 100755 index 8629c08ad..000000000 --- a/frontend/lang/gear/gears/en.php +++ /dev/null @@ -1,60 +0,0 @@ - "Gear", - // error banner zone - "gear_gear_API_error_-1" => "Connection to API not possible. Failed to execute cURL request", - "gear_gear_API_error_-2" => "API return code was not 200", - "gear_gear_error_addGear_-3" => "Nickname already exists. Please verify nickname", - "gear_gear_error_editGear_-3" => "Nickname and type field required", - // info banner zone - "gear_gear_info_searchGear_NULL" => "Gear not found", - "gear_gear_info_fromGear_invalidGear" => "Gear not found", - // success banner zone - "gear_gear_success_gearAdded" => "Gear added.", - "gear_gear_success_gearEdited" => "Gear edited.", - "gear_gear_success_gearDeleted" => "Gear deleted.", - // add gear zone - "gear_gear_buttonLabel_addGear" => "Add gear:", - "gear_gear_button_addGear" => "Add gear", - "gear_gear_modal_addGear_title" => "Add gear", - "gear_gear_modal_addEditGear_brandLabel" => "Brand", - "gear_gear_modal_addEditGear_brandPlaceholder" => "Brand (max 45 characters)", - "gear_gear_modal_addEditGear_modelLabel" => "Model", - "gear_gear_modal_addEditGear_modelPlaceholder" => "Model (max 45 characters)", - "gear_gear_modal_addEditGear_nicknameLabel" => "Nickname", - "gear_gear_modal_addEditGear_nicknamePlaceholder" => "Nickname (max 45 characters)", - "gear_gear_modal_addEditUser_gearTypeLabel" => "Gear type", - "gear_gear_modal_addEditUser_gearTypeOption1" => "Bike", - "gear_gear_modal_addEditUser_gearTypeOption2" => "Shoes", - "gear_gear_modal_addEditUser_gearTypeOption3" => "Wetsuit", - "gear_gear_modal_addEditGear_dateLabel" => "Created date", - "gear_gear_modal_addEditGear_datePlaceholder" => "Date", - // search gear zone - "gear_gear_buttonLabel_searchGear" => "Search gear by nickname:", - "gear_gear_button_searchGear" => "Search", - "gear_gear_form_searchGear_nicknamePlaceholder" => "Nickname", - // list gears - // error banner zone - "gear_gear_error_listGear_-1-2" => "It was not possible to list gear", - // info banner zone - "gear_gear_error_listGear_-3" => "Gear does not exist", - // list - "gear_gear_list_title1" => "There is a total of", - "gear_gear_list_title2" => " gear(s)", - "gear_gear_list_title3" => " per page)", - "gear_gear_gear_type" => "Type: ", - "gear_gear_list_isactive" => "Active", - "gear_gear_list_isinactive" => "Inactive", - "gear_gear_list_button_components" => "Gear components", - // edit gear zone - "gear_gear_modal_editGear_title" => "Edit gear", - "gear_gear_modal_editUser_gearIsActiveLabel" => "Is active", - "gear_gear_modal_editUser_gearIsActiveOption1" => "Yes", - "gear_gear_modal_editUser_gearIsActiveOption2" => "No", - // delete gear zone - "gear_gear_modal_deleteGear_title" => "Delete gear", - "gear_gear_modal_deleteGear_body" => "Are you sure you want to delete gear" -]; diff --git a/frontend/lang/inc/Template-Top/en.php b/frontend/lang/inc/Template-Top/en.php deleted file mode 100755 index 512fb6bcc..000000000 --- a/frontend/lang/inc/Template-Top/en.php +++ /dev/null @@ -1,20 +0,0 @@ - "Search", - "template_top_global_searchUserModal_title" => "Search user", - "template_top_global_searchUserModal_usernameLabel" => "Username", - "template_top_global_searchUserModal_usernamePlaceholder" => "Username", - "template_top_navbar_gear" => "Gear", - "template_top_navbar_login" => "Login", - "template_top_navbar_profile" => "Profile", - "template_top_navbar_settings" => "Settings", - "template_top_navbar_logout" => "Logout", - // back section - "template_top_global_back" => "Back", - "template_top_global_requiredFields" => "Required field(s)", - "template_top_global_close" => "Close", - "template_top_global_button_listAll" => "List all", - "template_top_warning_zone" => "Alpha software, some features might not work has expected" -]; diff --git a/frontend/lang/index/en.php b/frontend/lang/index/en.php deleted file mode 100755 index a72f23e0f..000000000 --- a/frontend/lang/index/en.php +++ /dev/null @@ -1,40 +0,0 @@ - "Feed", - "index_userZone_thisWeekDistances_title" => "This week distances", - "index_userZone_thisWeekDistances_run" => "Run", - "index_userZone_thisWeekDistances_bike" => "Bike", - "index_userZone_thisWeekDistances_swim" => "Swim", - "index_userZone_thisMonthDistances_title" => "This month distances", - // add activity - "index_sidebar_addActivity" => "Add Activity", - "index_sidebar_addActivity_modal_addGpxFile_placeholder" => "Add GPX activity", - // error banner zone - "index_sidebar_addActivity_API_error_-1" => "Connection to API not possible. Failed to execute cURL request", - "index_sidebar_addActivity_API_error_-2" => "API return code was not 200", - "index_sidebar_addActivity_GPXError_-3" => "Invalid GPX file or could not load the file", - "index_sidebar_addActivity_GPXError_-4" => "Invalid GPX file or could not load the file", - "index_sidebar_addActivity_createStreams_error_-5-6-7-8-9-10-11" => "Unable to create activity stream", - "index_sidebar_invalidActivity" => "Invalid activity. Activity ID not valid or you might not have access to see it", - "index_sidebar_noUsersFound" => "No user found with that username", - // info banner zone - "index_sidebar_addActivity_info_searchActivity_NULL" => "Activities not found", - "index_sidebar_info_userNotFound" => "User not found", - // success banner zone - "index_sidebar_addActivity_success_activityAdded" => "Activity added.", - "index_sidebar_addActivity_success_activityDeleted" => "Activity deleted.", - // radio zone - "activity_radio_userActivities" => "My activities", - "activity_radio_followersActivities" => "Followers activities", - // activities zone - "index_activities_type_title" => "Activity added.", - "index_activities_detail_distance" => "Distance", - "index_activities_detail_time" => "Time", - "index_activities_detail_elevation_gain" => "Elevation gain", - "index_activities_detail_pace" => "Pace", - "index_activities_stravaText1" => "Strava activitity, see it ", - "index_activities_stravaText2" => "here" -]; diff --git a/frontend/lang/login/en.php b/frontend/lang/login/en.php deleted file mode 100755 index 923ff9bdb..000000000 --- a/frontend/lang/login/en.php +++ /dev/null @@ -1,20 +0,0 @@ - "User session expired", - "login_error_access_token_not_set" => "Unable to login. Access token not set", - "login_error_user_inactive" => "User is inactive. Unable to login", - "login_error_incorrect_user_credentials" => "Incorrect user credentials. Check username and password", - "login_error_undefined" => "Unable to login. Undefined", - "login_API_error_-1" => "Connection to API not possible. Failed to execute cURL request", - "login_API_error_-2" => "API return code was not 200", - "login_API_error_-3" => "User state is inactive. Unable to login", - "login_subtitle" => "Sign-in bellow", - "login_insert_username" => "Insert username", - "login_password" => "Password", - "login_neverExpires" => "Remember sign in", - "login_login" => "Sign in", - "login_signup_text" => "Looking for signing up?", - "login_signup_button" => "Sign up", - // Add more translations here -]; diff --git a/frontend/lang/settings/en.php b/frontend/lang/settings/en.php deleted file mode 100755 index a26f9a897..000000000 --- a/frontend/lang/settings/en.php +++ /dev/null @@ -1,19 +0,0 @@ - "Settings", - "settings_sidebar_users" => "Users", - "settings_sidebar_global" => "Global settings", - "settings_sidebar_profileSettings" => "My profile", - "settings_sidebar_securitySettings" => "Security", - "settings_sidebar_integrationsSettings" => "Integrations", - // error banner zone - "settings_API_error_-1" => "Connection to API not possible. Failed to execute cURL request", - "settings_API_error_-2" => "API return code was not 200", - // info banner zone - - // success banner zone - -]; diff --git a/frontend/lang/settings/inc/integration-settings/en.php b/frontend/lang/settings/inc/integration-settings/en.php deleted file mode 100644 index ef5da7783..000000000 --- a/frontend/lang/settings/inc/integration-settings/en.php +++ /dev/null @@ -1,17 +0,0 @@ - "Strava linked with success", - "settings_integration_settings_success_stravaGear" => "Strava gear retrieved", - "settings_integration_settings_success_stravaActivities" => "Strava activities retrieval background process started", - // card zone - "settings_integration_settings_strava_title" => "Strava", - "settings_integration_settings_strava_body" => "Strava is an American internet service for tracking physical exercise which incorporates social network features.", - "settings_integration_settings_strava_body_linked" => "Strava is linked to your account", - // general - "settings_integration_settings_connect_button" => "Connect", - "settings_integration_settings_retrieve_last_week_activities_button" => "Retrieve last week activities", - "settings_integration_settings_retrieve_gear_button" => "Retrieve gear", -]; \ No newline at end of file diff --git a/frontend/lang/settings/inc/profile-settings/en.php b/frontend/lang/settings/inc/profile-settings/en.php deleted file mode 100644 index 056194706..000000000 --- a/frontend/lang/settings/inc/profile-settings/en.php +++ /dev/null @@ -1,50 +0,0 @@ - "It was not possible to delete profile photo on filesystem. Database not updated", - "settings_profile_settings_error_editProfile_-3" => "Photo path invalid", - "settings_profile_settings_error_editProfile_-4" => "Only photos with extension .jpg, .jpeg e .png are allowed", - "settings_profile_settings_error_editProfile_-5" => "It was not possible to upload photo", - // info banner zone - - // success banner zone - "settings_sidebar_profile_success_profilePhotoDeleted" => "Profile photo deleted.", - "settings_sidebar_profile_success_profileEdited" => "Profile edited", - - // edit profile modal zone - "settings_sidebar_profile_editProfile_title" => "Edit profile", - "settings_sidebar_profile_photo_label" => "Profile photo", - "settings_sidebar_profile_username_label" => "Username", - "settings_sidebar_profile_username_placeholder" => "Username (max 45 characters)", - "settings_sidebar_profile_name_label" => "Name", - "settings_sidebar_profile_name_placeholder" => "Name (max 45 characters)", - "settings_sidebar_profile_email_label" => "Email", - "settings_sidebar_profile_email_placeholder" => "Email (max 45 characters)", - "settings_sidebar_profile_city_label" => "Town/city", - "settings_sidebar_profile_city_placeholder" => "Town/city (max 45 characters)", - "settings_sidebar_profile_birthdate_label" => "Birth date", - "settings_sidebar_profile_gender_label" => "Gender", - "settings_sidebar_profile_gender_option1" => "Male", - "settings_sidebar_profile_gender_option2" => "Female", - "settings_sidebar_profile_preferredLanguage_label" => "Prefered language", - "settings_sidebar_profile_preferredLanguage_option1" => "English", - "settings_sidebar_profile_preferredLanguage_option2" => "Portuguese", - // profile zone - "settings_sidebar_profile_deleteProfilePhoto" => "Delete profile photo", - "settings_sidebar_profile_modal_title_deleteProfilePhoto" => "Delete profile photo", - "settings_sidebar_profile_modal_body_deleteProfilePhoto" => "Are you sure you want to delete the profile photo?", - "settings_sidebar_profile_button_editprofile" => "Edit profile", - "settings_sidebar_profile_gender_male" => "Male", - "settings_sidebar_profile_gender_female" => "Female", - "settings_sidebar_profile_access_type_regular_user" => "Regular user", - "settings_sidebar_profile_access_type_admin" => "Admin", - "settings_sidebar_profile_username_subtitle" => "Username: ", - "settings_sidebar_profile_email_subtitle" => "Email: ", - "settings_sidebar_profile_gender_subtitle" => "Gender: ", - "settings_sidebar_profile_birthdate_subtitle" => "Birthdate: ", - "settings_sidebar_profile_city_subtitle" => "City: ", - "settings_sidebar_profile_access_type_subtitle" => "Access type: ", - "settings_sidebar_profile_preferredlanguage_subtitle" => "Preferred language: ", -]; diff --git a/frontend/lang/settings/inc/security-settings/en.php b/frontend/lang/settings/inc/security-settings/en.php deleted file mode 100644 index 705d79ac4..000000000 --- a/frontend/lang/settings/inc/security-settings/en.php +++ /dev/null @@ -1,24 +0,0 @@ - "Passwords don't match", - "settings_security_settings_error_password_complexity-4" => "Password complexity not met", - - // info banner zone - "settings_security_settings_info_password_requirements" => "Password requirements includes:", - "settings_security_settings_info_password_requirements_characters" => " - 8 characters;", - "settings_security_settings_info_password_requirements_capital_letters" => " - 1 capital letter;", - "settings_security_settings_info_password_requirements_numbers" => " - 1 number;", - "settings_security_settings_info_password_requirements_special_characters" => " - 1 special character.", - - // success banner zone - "settings_security_settings_success_password_edited" => "User password edited", - - // form change password - "settings_security_settings_subtitle_change_password" => "Change password", - "settings_security_settings_subtitle_change_password_password" => "Password", - "settings_security_settings_subtitle_change_password_repeat_password" => "Repeat password", - "settings_security_settings_subtitle_change_password_button" => "Change password", -]; diff --git a/frontend/lang/settings/inc/users-settings/en.php b/frontend/lang/settings/inc/users-settings/en.php deleted file mode 100644 index f3859de39..000000000 --- a/frontend/lang/settings/inc/users-settings/en.php +++ /dev/null @@ -1,86 +0,0 @@ - "Username already exists. Please verify username", - "settings_users_settings_error_addEditUser_-4" => "Photo path invalid", - "settings_users_settings_error_addEditUser_-5" => "Only photos with extension .jpg, .jpeg e .png are allowed", - "settings_users_settings_error_addEditUser_-6" => "It was not possible to upload photo", - "settings_users_settings_error_editUser_-3" => "Username and type field required", - "settings_users_settings_error_deleteUser_-3" => "User cannot delete himself", - "settings_users_settings_error_deleteUser_-409" => "User has dependencies. It is not possible to delete", - "settings_users_settings_error_deleteUserPhoto_-3" => "It was not possible to delete user photo on filesystem. Database not updated", - "settings_users_settings_error_passwords_dont_match-3" => "Passwords don't match", - "settings_users_settings_error_password_complexity-4" => "Password complexity not met", - "settings_users_settings_error_user_id_not_valid-5" => "User ID not valid", - // info banner zone - "settings_users_settings_info_userDeleted_photoNotDeleted" => "User deleted. It was not possible to delete user photo from filesystem.", - "settings_users_settings_error_searchUser_NULL" => "User not found", - // success banner zone - "settings_users_settings_success_userAdded" => "User added.", - "settings_users_settings_success_userEdited" => "User edited.", - "settings_users_settings_success_userDeleted" => "User deleted.", - "settings_users_settings_success_userPhotoDeleted" => "User photo deleted.", - "settings_users_settings_success_password_edited" => "User password edited", - // users zone - "settings_users_settings_new" => "New user", - "settings_users_settings_search" => "Search", - "settings_sidebar_button_addUser" => "Add user", - // add user modal - "settings_users_settings_modal_addUser_title" => "Add user", - "settings_users_settings_modal_addEditUser_photoLabel" => "User photo", - "settings_users_settings_modal_addEditUser_usernameLabel" => "Username", - "settings_users_settings_modal_addEditUser_usernamePlaceholder" => "Username (max 45 characters)", - "settings_users_settings_modal_addEditUser_emailLabel" => "Email", - "settings_users_settings_modal_addEditUser_emailPlaceholder" => "Email (max 45 characters)", - "settings_users_settings_modal_addEditUser_nameLabel" => "Name", - "settings_users_settings_modal_addEditUser_namePlaceholder" => "Name (max 45 characters)", - "settings_users_settings_modal_addEditUser_passwordLabel" => "Password", - "settings_users_settings_modal_addEditUser_passwordPlaceholder" => "Password", - "settings_users_settings_modal_addEditUser_cityLabel" => "Town/city", - "settings_users_settings_modal_addEditUser_cityPlaceholder" => "Town/city (max 45 characters)", - "settings_users_settings_modal_addEditUser_birthdateLabel" => "Birth date", - "settings_users_settings_modal_addEditUser_genderLabel" => "User gender", - "settings_users_settings_modal_addEditUser_genderOption1" => "Male", - "settings_users_settings_modal_addEditUser_genderOption2" => "Female", - "settings_users_settings_modal_addEditUser_preferredLanguageLabel" => "User prefered language", - "settings_users_settings_modal_addEditUser_preferredLanguageOption1" => "English", - "settings_users_settings_modal_addEditUser_preferredLanguageOption2" => "Portuguese", - "settings_users_settings_modal_addEditUser_typeLabel" => "User type", - "settings_users_settings_modal_addEditUser_typeOption1" => "Regular user", - "settings_users_settings_modal_addEditUser_typeOption2" => "Administrator", - "settings_users_settings_modal_addEditUser_notesLabel" => "Notes", - "settings_users_settings_modal_addEditUser_notesPlaceholder" => "Notes (max 250 characters)", - "settings_users_settings_modal_addEditUser_isActiveLabel" => "Is active", - "settings_users_settings_modal_addEditUser_isActiveOption1" => "Yes", - "settings_users_settings_modal_addEditUser_isActiveOption2" => "No", - // search user zone - "settings_sidebar_form_searchUser_usernamePlaceholder" => "Username", - // users list - "settings_users_settings_list_title1" => "There is a total of", - "settings_users_settings_list_title2" => " user(s)", - "settings_users_settings_list_title3" => " per page)", - "settings_users_settings_list_user_accesstype" => "Access type: ", - "settings_users_settings_list_isactive" => "Active", - "settings_users_settings_list_isinactive" => "Inactive", - // edit user zone - "settings_users_settings_modal_editUser_title" => "Edit user", - // delete user photo zone - "settings_users_settings_modal_editUser_deleteUserPhoto" => "Delete photo", - // delete user zone - "settings_users_settings_modal_deleteUser_title" => "Delete user", - "settings_users_settings_modal_deleteUser_body" => "Are you sure you want to delete user", - // edit user password zone - // info banner zone - "settings_users_settings_info_password_requirements" => "Password requirements includes:", - "settings_users_settings_info_password_requirements_characters" => " - 8 characters;", - "settings_users_settings_info_password_requirements_capital_letters" => " - 1 capital letter;", - "settings_users_settings_info_password_requirements_numbers" => " - 1 number;", - "settings_users_settings_info_password_requirements_special_characters" => " - 1 special character.", - // modal - "settings_users_settings_modal_changeUserPassword_title" => "Change password", - "settings_users_settings_modal_changeUserPassword_body" => "Change password for user ", - "settings_users_settings_modal_change_password_password" => "Password", - "settings_users_settings_modal_change_password_repeat_password" => "Repeat password", -]; diff --git a/frontend/lang/users/user/en.php b/frontend/lang/users/user/en.php deleted file mode 100755 index d5b8c40fe..000000000 --- a/frontend/lang/users/user/en.php +++ /dev/null @@ -1,70 +0,0 @@ - "User", - // error banner zone - "user_follow_API_error_-1" => "Connection to API not possible. Failed to execute cURL request", - "user_follow_API_error_-2" => "API return code was not 200", - // success banner zone - "user_follow_success" => "Request sent to follow user", - "user_unfollow_success" => "User unfollowed successfully", - "user_follow_request_declined" => "Follow request declined successfully", - "user_follow_request_accepted" => "Follow request accepted successfully", - // Stats zone - "user_stats_numberActivitiesMonth" => "This month activities", - // distances zone - "user_distances_zone_thisWeekDistances_title" => "Current week distances", - "user_distances_zone_thisWeekDistances_run" => "Run", - "user_distances_zone_thisWeekDistances_bike" => "Bike", - "user_distances_zone_thisWeekDistances_swim" => "Swim", - "user_distances_zone_thisMonthDistances_title" => "Current month distances", - // main zone - "user_main_nav_activities" => "Activities", - "user_main_nav_following" => "Following", - "user_main_nav_followers" => "Followers", - "user_main_nav_user_settings" => "User settings", - "user_main_nav_followStatus_follow" => "Follow", - "user_main_nav_followStatus_requestSent" => "Request sent", - "user_main_nav_followStatus_unfollow" => "Unfollow", - // follow modal - "user_follow_modal_title" => "Follow user", - "user_follow_modal_body" => "Are you sure you want to follow user ", - // cancel follow modal - "user_cancelFollow_modal_title" => "Cancel follow request", - "user_cancelFollow_modal_body" => "Are you sure you want to cancel follow request for user ", - // unfollow modal - "user_unfollow_modal_title" => "Unfollow user", - "user_unfollow_modal_body" => "Are you sure you want to unfollow user ", - // activities zone - "user_activities_zone_ops" => "Ops...", - "user_activities_zone_noActivitiesFound" => "No activities found for this period", - "user_activities_zone_dateRange1" => "Activities from ", - "user_activities_zone_dateRange2" => " to ", - "user_activities_zone_date_current" => "This week", - "user_activities_zone_date_oneYearAgo" => "One year ago", - "user_activities_zone_detail_distance" => "Distance", - "user_activities_zone_detail_time" => "Time", - "user_activities_zone_detail_elevation_gain" => "Elevation gain", - "user_activities_zone_detail_pace" => "Pace", - "user_activities_zone_stravaText1" => "Strava activitity, see it ", - "user_activities_zone_stravaText2" => "here", - // following zone - "user_following_zone_noFollowers" => "No followers found for this user", - "user_following_zone_noFollowing" => "This user is not following anyone", - "user_following_zone_requestAccepted" => "Accepted", - "user_following_zone_requestPending" => "Request pending", - // modal delete following - "user_deleteFollowing_modal_title" => "Delete following", - "user_deleteFollowing_modal_body" => "Are you sure you want to delete following user", - // modal delete follower - "user_deleteFollower_modal_title" => "Delete follower", - "user_deleteFollower_modal_body" => "Are you sure you want to delete follower user", - // modal accept user request - "user_acceptUserRequest_modal_title" => "Accept user request", - "user_acceptUserRequest_modal_body" => "Are you sure you want to accept follow request from user", - // modal decline user request - "user_declineUserRequest_modal_title" => "Decline user request", - "user_declineUserRequest_modal_body" => "Are you sure you want to decline follow request from user", -]; diff --git a/frontend/login.php b/frontend/login.php deleted file mode 100755 index a338ee4d7..000000000 --- a/frontend/login.php +++ /dev/null @@ -1,138 +0,0 @@ - - - - -
- - - - - - - - - - -
-

Endurain

-

-
- -
- - -
-
-
- - -
-
-
- - -
-
- - -
-
- - \ No newline at end of file diff --git a/frontend/logout.php b/frontend/logout.php deleted file mode 100755 index 7725337bd..000000000 --- a/frontend/logout.php +++ /dev/null @@ -1,23 +0,0 @@ -=0.10.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", + "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", + "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz", + "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz", + "integrity": "sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", + "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/vue-fontawesome": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.6.tgz", + "integrity": "sha512-akrL7lTroyNpPkoHtvK2UpsMzJr6jXdHaQ0YdcwqDsB8jdwlpNHZYijpOUd9KJsARr+VB3WXY4EyObepqJ4ytQ==", + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "vue": ">= 3.0.0 < 4" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@intlify/core-base": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.13.1.tgz", + "integrity": "sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==", + "dependencies": { + "@intlify/message-compiler": "9.13.1", + "@intlify/shared": "9.13.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.13.1.tgz", + "integrity": "sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==", + "dependencies": { + "@intlify/shared": "9.13.1", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.13.1.tgz", + "integrity": "sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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==" + }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/@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, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz", + "integrity": "sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz", + "integrity": "sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz", + "integrity": "sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz", + "integrity": "sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz", + "integrity": "sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz", + "integrity": "sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz", + "integrity": "sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz", + "integrity": "sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz", + "integrity": "sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz", + "integrity": "sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz", + "integrity": "sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz", + "integrity": "sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz", + "integrity": "sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz", + "integrity": "sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz", + "integrity": "sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz", + "integrity": "sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.2.tgz", + "integrity": "sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==", + "dev": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true + }, + "node_modules/@types/leaflet": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", + "integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", + "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/expect": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.5.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.24.tgz", + "integrity": "sha512-vbW/tgbwJYj62N/Ww99x0zhFTkZDTcGh3uwJEuadZ/nF9/xuFMC4693P9r+3sxGXISABpDKvffY5ApH9pmdd1A==", + "dependencies": { + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.24", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-core/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/@vue/compiler-dom": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.24.tgz", + "integrity": "sha512-4XgABML/4cNndVsQndG6BbGN7+EoisDwi3oXNovqL/4jdNhwvP8/rfRMTb6FxkxIxUUtg6AI1/qZvwfSjxJiWA==", + "dependencies": { + "@vue/compiler-core": "3.4.24", + "@vue/shared": "3.4.24" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.24.tgz", + "integrity": "sha512-nRAlJUK02FTWfA2nuvNBAqsDZuERGFgxZ8sGH62XgFSvMxO2URblzulExsmj4gFZ8e+VAyDooU9oAoXfEDNxTA==", + "dependencies": { + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.24", + "@vue/compiler-dom": "3.4.24", + "@vue/compiler-ssr": "3.4.24", + "@vue/shared": "3.4.24", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-sfc/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/@vue/compiler-sfc/node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.24.tgz", + "integrity": "sha512-ZsAtr4fhaUFnVcDqwW3bYCSDwq+9Gk69q2r/7dAHDrOMw41kylaMgOP4zRnn6GIEJkQznKgrMOGPMFnLB52RbQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.24", + "@vue/shared": "3.4.24" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", + "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" + }, + "node_modules/@vue/eslint-config-prettier": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-8.0.0.tgz", + "integrity": "sha512-55dPqtC4PM/yBjhAr+yEw6+7KzzdkBuLmnhBrDfp4I48+wy+Giqqj9yUr5T2uD/BkBROjjmqnLZmXRdOx/VtQg==", + "dev": true, + "dependencies": { + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0" + }, + "peerDependencies": { + "eslint": ">= 8.0.0", + "prettier": ">= 3.0.0" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.24.tgz", + "integrity": "sha512-nup3fSYg4i4LtNvu9slF/HF/0dkMQYfepUdORBcMSsankzRPzE7ypAFurpwyRBfU1i7Dn1kcwpYsE1wETSh91g==", + "dependencies": { + "@vue/shared": "3.4.24" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.24.tgz", + "integrity": "sha512-c7iMfj6cJMeAG3s5yOn9Rc5D9e2/wIuaozmGf/ICGCY3KV5H7mbTVdvEkd4ZshTq7RUZqj2k7LMJWVx+EBiY1g==", + "dependencies": { + "@vue/reactivity": "3.4.24", + "@vue/shared": "3.4.24" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.24.tgz", + "integrity": "sha512-uXKzuh/Emfad2Y7Qm0ABsLZZV6H3mAJ5ZVqmAOlrNQRf+T5mxpPGZBfec1hkP41t6h6FwF6RSGCs/gd8WbuySQ==", + "dependencies": { + "@vue/runtime-core": "3.4.24", + "@vue/shared": "3.4.24", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.24.tgz", + "integrity": "sha512-H+DLK4sQF6sRgzKyofmlEVBIV/9KrQU6HIV7nt6yIwSGGKvSwlV8pqJlebUKLpbXaNHugdSfAbP6YmXF69lxow==", + "dependencies": { + "@vue/compiler-ssr": "3.4.24", + "@vue/shared": "3.4.24" + }, + "peerDependencies": { + "vue": "3.4.24" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz", + "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw==" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.5.tgz", + "integrity": "sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==", + "dev": true, + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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 + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/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 + }, + "node_modules/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 + }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chart.js": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/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, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dev": true, + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/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": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/editorconfig/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/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.25.0.tgz", + "integrity": "sha512-tDWlx14bVe6Bs+Nnh3IGrD+hb11kf2nukfm6jLsmJIhmiRQ1SUaksvwY9U5MvPB0pcrg0QK0xapQkfITs3RKOA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.0", + "vue-eslint-parser": "^9.4.2", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vue/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-vue/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": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-vue/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-vue/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/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/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 + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/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 + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/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, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/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, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz", + "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==", + "dev": true, + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.7", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.16.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mlly": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nopt": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.9.tgz", + "integrity": "sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/pinia": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz", + "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.0.tgz", + "integrity": "sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==", + "dev": true, + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.6.1", + "pathe": "^1.1.2" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/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, + "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/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz", + "integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.16.4", + "@rollup/rollup-android-arm64": "4.16.4", + "@rollup/rollup-darwin-arm64": "4.16.4", + "@rollup/rollup-darwin-x64": "4.16.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.16.4", + "@rollup/rollup-linux-arm-musleabihf": "4.16.4", + "@rollup/rollup-linux-arm64-gnu": "4.16.4", + "@rollup/rollup-linux-arm64-musl": "4.16.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.16.4", + "@rollup/rollup-linux-riscv64-gnu": "4.16.4", + "@rollup/rollup-linux-s390x-gnu": "4.16.4", + "@rollup/rollup-linux-x64-gnu": "4.16.4", + "@rollup/rollup-linux-x64-musl": "4.16.4", + "@rollup/rollup-win32-arm64-msvc": "4.16.4", + "@rollup/rollup-win32-ia32-msvc": "4.16.4", + "@rollup/rollup-win32-x64-msvc": "4.16.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true + }, + "node_modules/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, + "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": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tinybench": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", + "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/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 + }, + "node_modules/vite": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz", + "integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.5.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.4.24", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.24.tgz", + "integrity": "sha512-NPdx7dLGyHmKHGRRU5bMRYVE+rechR+KDU5R2tSTNG36PuMwbfAJ+amEvOAw7BPfZp5sQulNELSLm5YUkau+Sg==", + "dependencies": { + "@vue/compiler-dom": "3.4.24", + "@vue/compiler-sfc": "3.4.24", + "@vue/runtime-dom": "3.4.24", + "@vue/server-renderer": "3.4.24", + "@vue/shared": "3.4.24" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.14.tgz", + "integrity": "sha512-DInfgOyXlMyliyqAAD9frK28tTfch0+tMi4qoWJcZlRxUf+NFAtraJBnAsKLep+FOyLMiajkhfyEb3xLK08i7w==", + "dev": true + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", + "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/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": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-eslint-parser/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-eslint-parser/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/vue-i18n": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.13.1.tgz", + "integrity": "sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==", + "dependencies": { + "@intlify/core-base": "9.13.1", + "@intlify/shared": "9.13.1", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz", + "integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==", + "dependencies": { + "@vue/devtools-api": "^6.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/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 + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/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 + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 000000000..ff7ceb109 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,42 @@ +{ + "name": "frontend_vue", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test:unit": "vitest", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", + "format": "prettier --write src/" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-brands-svg-icons": "^6.5.1", + "@fortawesome/free-regular-svg-icons": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/vue-fontawesome": "^3.0.6", + "bootstrap": "^5.3.2", + "chart.js": "^4.4.1", + "crypto-js": "^4.2.0", + "leaflet": "^1.9.4", + "pinia": "^2.1.7", + "vue": "^3.4.15", + "vue-i18n": "^9.9.1", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.3.3", + "@types/leaflet": "^1.9.8", + "@vitejs/plugin-vue": "^5.0.3", + "@vue/eslint-config-prettier": "^8.0.0", + "@vue/test-utils": "^2.4.4", + "eslint": "^8.49.0", + "eslint-plugin-vue": "^9.17.0", + "jsdom": "^24.0.0", + "prettier": "^3.0.3", + "vite": "^5.0.11", + "vitest": "^1.2.2" + } +} diff --git a/frontend/img/logo/logo.png b/frontend/public/logo/logo.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/logo/logo.png rename to frontend/public/logo/logo.png diff --git a/frontend/settings/inc/integration-settings.php b/frontend/settings/inc/integration-settings.php deleted file mode 100644 index d0a857ba3..000000000 --- a/frontend/settings/inc/integration-settings.php +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - -
-
-
- Compatible with Strava image -
-

-

- "> - -
- - - - -
-
-
-
\ No newline at end of file diff --git a/frontend/settings/inc/profile-settings.php b/frontend/settings/inc/profile-settings.php deleted file mode 100644 index 847f6b4bd..000000000 --- a/frontend/settings/inc/profile-settings.php +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - - - - - - - - - - -
-
-
- alt="Profile picture" width="180" height="180" class="rounded-circle"> -
- - - - - - - - - - - "> - - - - - -
- - -
-

-

-

-

-

-

-

-

-

-
\ No newline at end of file diff --git a/frontend/settings/inc/security-settings.php b/frontend/settings/inc/security-settings.php deleted file mode 100644 index 9689fd59e..000000000 --- a/frontend/settings/inc/security-settings.php +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - -

- - - - -
- - - " required> - - - - " required> - -

*

- - -
\ No newline at end of file diff --git a/frontend/settings/inc/users-settings.php b/frontend/settings/inc/users-settings.php deleted file mode 100644 index bbc127fd9..000000000 --- a/frontend/settings/inc/users-settings.php +++ /dev/null @@ -1,655 +0,0 @@ - 0){ - if($_POST["passUserEditAdmin"] == $_POST["passRepeatUserEditAdmin"]){ - // Check password complexity - if (preg_match('/^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/', $_POST["passUserEditAdmin"])) { - $editUserPasswordAdminAction = editUserPassword($_GET["userID"], $_POST["passUserEditAdmin"]); - }else{ - $editUserPasswordAdminAction = -4; - } - }else{ - $editUserPasswordAdminAction = -3; - } - }else{ - $editUserPasswordAdminAction = -5; - } - } -?> - - - - - - - - - - - - - - - - -
-
- - - - - -
-
-
- - - - - -
-
-
- - - - -
-

( :

- -
    - -
  • -
    - alt="userPicture" class="rounded-circle" width="55" height="55"> -
    -
    - -
    - -
    -
    -
    - - - - - - - - "> - - - - - - "> - - - - - - "> - - - - -
    -
  • - -
- -
- - - \ No newline at end of file diff --git a/frontend/settings/settings.php b/frontend/settings/settings.php deleted file mode 100755 index 92f8d3109..000000000 --- a/frontend/settings/settings.php +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 000000000..7bda450a9 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/frontend/img/avatar/bicycle1.png b/frontend/src/assets/avatar/bicycle1.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/avatar/bicycle1.png rename to frontend/src/assets/avatar/bicycle1.png diff --git a/frontend/img/avatar/bicycle2.png b/frontend/src/assets/avatar/bicycle2.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/avatar/bicycle2.png rename to frontend/src/assets/avatar/bicycle2.png diff --git a/frontend/img/avatar/female1.png b/frontend/src/assets/avatar/female1.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/avatar/female1.png rename to frontend/src/assets/avatar/female1.png diff --git a/frontend/img/avatar/male1.png b/frontend/src/assets/avatar/male1.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/avatar/male1.png rename to frontend/src/assets/avatar/male1.png diff --git a/frontend/img/avatar/running_shoe1.png b/frontend/src/assets/avatar/running_shoe1.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/avatar/running_shoe1.png rename to frontend/src/assets/avatar/running_shoe1.png diff --git a/frontend/img/avatar/running_shoe2.png b/frontend/src/assets/avatar/running_shoe2.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/avatar/running_shoe2.png rename to frontend/src/assets/avatar/running_shoe2.png diff --git a/frontend/img/avatar/wetsuit1.png b/frontend/src/assets/avatar/wetsuit1.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/avatar/wetsuit1.png rename to frontend/src/assets/avatar/wetsuit1.png diff --git a/frontend/img/avatar/wetsuit2.png b/frontend/src/assets/avatar/wetsuit2.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/avatar/wetsuit2.png rename to frontend/src/assets/avatar/wetsuit2.png diff --git a/frontend/img/strava/api_logo_cptblWith_strava_horiz_light.png b/frontend/src/assets/strava/api_logo_cptblWith_strava_horiz_light.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/strava/api_logo_cptblWith_strava_horiz_light.png rename to frontend/src/assets/strava/api_logo_cptblWith_strava_horiz_light.png diff --git a/frontend/img/strava/api_logo_cptblWith_strava_stack_light.png b/frontend/src/assets/strava/api_logo_cptblWith_strava_stack_light.png old mode 100755 new mode 100644 similarity index 100% rename from frontend/img/strava/api_logo_cptblWith_strava_stack_light.png rename to frontend/src/assets/strava/api_logo_cptblWith_strava_stack_light.png diff --git a/frontend/src/components/Activities/ActivityMapComponent.vue b/frontend/src/components/Activities/ActivityMapComponent.vue new file mode 100644 index 000000000..7eeff0a32 --- /dev/null +++ b/frontend/src/components/Activities/ActivityMapComponent.vue @@ -0,0 +1,102 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Activities/ActivityStreamsLineChartComponent.vue b/frontend/src/components/Activities/ActivityStreamsLineChartComponent.vue new file mode 100644 index 000000000..90a1fef98 --- /dev/null +++ b/frontend/src/components/Activities/ActivityStreamsLineChartComponent.vue @@ -0,0 +1,168 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Activities/ActivitySummaryComponent.vue b/frontend/src/components/Activities/ActivitySummaryComponent.vue new file mode 100644 index 000000000..013d2809e --- /dev/null +++ b/frontend/src/components/Activities/ActivitySummaryComponent.vue @@ -0,0 +1,264 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Activities/UserDistanceStatsComponent.vue b/frontend/src/components/Activities/UserDistanceStatsComponent.vue new file mode 100644 index 000000000..236e13bc3 --- /dev/null +++ b/frontend/src/components/Activities/UserDistanceStatsComponent.vue @@ -0,0 +1,68 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Alerts/ErrorAlertComponent.vue b/frontend/src/components/Alerts/ErrorAlertComponent.vue new file mode 100644 index 000000000..12b7836c7 --- /dev/null +++ b/frontend/src/components/Alerts/ErrorAlertComponent.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Alerts/InfoAlertComponent.vue b/frontend/src/components/Alerts/InfoAlertComponent.vue new file mode 100644 index 000000000..daf4f6770 --- /dev/null +++ b/frontend/src/components/Alerts/InfoAlertComponent.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Alerts/LoadingAlertComponent.vue b/frontend/src/components/Alerts/LoadingAlertComponent.vue new file mode 100644 index 000000000..0645f9526 --- /dev/null +++ b/frontend/src/components/Alerts/LoadingAlertComponent.vue @@ -0,0 +1,28 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Alerts/SuccessAlertComponent.vue b/frontend/src/components/Alerts/SuccessAlertComponent.vue new file mode 100644 index 000000000..c2d7f759a --- /dev/null +++ b/frontend/src/components/Alerts/SuccessAlertComponent.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/BackButtonComponent.vue b/frontend/src/components/BackButtonComponent.vue new file mode 100644 index 000000000..d3f6eb1ac --- /dev/null +++ b/frontend/src/components/BackButtonComponent.vue @@ -0,0 +1,28 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Followers/FollowersListComponent.vue b/frontend/src/components/Followers/FollowersListComponent.vue new file mode 100644 index 000000000..ea7e5523c --- /dev/null +++ b/frontend/src/components/Followers/FollowersListComponent.vue @@ -0,0 +1,233 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/FooterComponent.vue b/frontend/src/components/FooterComponent.vue new file mode 100644 index 000000000..3d04931f3 --- /dev/null +++ b/frontend/src/components/FooterComponent.vue @@ -0,0 +1,137 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/LoadingComponent.vue b/frontend/src/components/LoadingComponent.vue new file mode 100644 index 000000000..84fe57fe1 --- /dev/null +++ b/frontend/src/components/LoadingComponent.vue @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/frontend/src/components/NavbarComponent.vue b/frontend/src/components/NavbarComponent.vue new file mode 100644 index 000000000..b5307ec2f --- /dev/null +++ b/frontend/src/components/NavbarComponent.vue @@ -0,0 +1,114 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/NoItemsFoundComponents.vue b/frontend/src/components/NoItemsFoundComponents.vue new file mode 100644 index 000000000..00c0bd325 --- /dev/null +++ b/frontend/src/components/NoItemsFoundComponents.vue @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/frontend/src/components/Settings/SettingsIntegrationsZone.vue b/frontend/src/components/Settings/SettingsIntegrationsZone.vue new file mode 100644 index 000000000..d3c81bf58 --- /dev/null +++ b/frontend/src/components/Settings/SettingsIntegrationsZone.vue @@ -0,0 +1,125 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Settings/SettingsPasswordRequirementsComponent.vue b/frontend/src/components/Settings/SettingsPasswordRequirementsComponent.vue new file mode 100644 index 000000000..dc62ddb17 --- /dev/null +++ b/frontend/src/components/Settings/SettingsPasswordRequirementsComponent.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/frontend/src/components/Settings/SettingsSecurityZone.vue b/frontend/src/components/Settings/SettingsSecurityZone.vue new file mode 100644 index 000000000..be5d65618 --- /dev/null +++ b/frontend/src/components/Settings/SettingsSecurityZone.vue @@ -0,0 +1,119 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Settings/SettingsSideBarComponent.vue b/frontend/src/components/Settings/SettingsSideBarComponent.vue new file mode 100644 index 000000000..1e5be8c35 --- /dev/null +++ b/frontend/src/components/Settings/SettingsSideBarComponent.vue @@ -0,0 +1,60 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Settings/SettingsUserProfileZone.vue b/frontend/src/components/Settings/SettingsUserProfileZone.vue new file mode 100644 index 000000000..fa2e2aa43 --- /dev/null +++ b/frontend/src/components/Settings/SettingsUserProfileZone.vue @@ -0,0 +1,273 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Settings/SettingsUsersZone.vue b/frontend/src/components/Settings/SettingsUsersZone.vue new file mode 100644 index 000000000..d23270b02 --- /dev/null +++ b/frontend/src/components/Settings/SettingsUsersZone.vue @@ -0,0 +1,341 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Settings/SettingsUsersZone/UsersListComponent.vue b/frontend/src/components/Settings/SettingsUsersZone/UsersListComponent.vue new file mode 100644 index 000000000..b7d01c61d --- /dev/null +++ b/frontend/src/components/Settings/SettingsUsersZone/UsersListComponent.vue @@ -0,0 +1,384 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Toasts/ErrorToastComponent.vue b/frontend/src/components/Toasts/ErrorToastComponent.vue new file mode 100644 index 000000000..4ed22fb90 --- /dev/null +++ b/frontend/src/components/Toasts/ErrorToastComponent.vue @@ -0,0 +1,53 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Toasts/LoadingToastComponent.vue b/frontend/src/components/Toasts/LoadingToastComponent.vue new file mode 100644 index 000000000..9d3c14dd0 --- /dev/null +++ b/frontend/src/components/Toasts/LoadingToastComponent.vue @@ -0,0 +1,54 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Toasts/SuccessToastComponent.vue b/frontend/src/components/Toasts/SuccessToastComponent.vue new file mode 100644 index 000000000..227d4a7a5 --- /dev/null +++ b/frontend/src/components/Toasts/SuccessToastComponent.vue @@ -0,0 +1,53 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Users/UserAvatarComponent.vue b/frontend/src/components/Users/UserAvatarComponent.vue new file mode 100644 index 000000000..77d3d4382 --- /dev/null +++ b/frontend/src/components/Users/UserAvatarComponent.vue @@ -0,0 +1,43 @@ + + + \ No newline at end of file diff --git a/frontend/src/i18n/en/activityView.json b/frontend/src/i18n/en/activityView.json new file mode 100644 index 000000000..5af32c4a6 --- /dev/null +++ b/frontend/src/i18n/en/activityView.json @@ -0,0 +1,14 @@ +{ + "labelGear": "Gear", + "labelGearNotSet": "Not set", + "modalLabelAddGear": "Add gear to activity", + "modalLabelSelectGear": "Select gear", + "modalButtonAddGear": "Add gear", + "modalLabelDeleteGear": "Delete gear from activity", + "modalLabelDeleteGearBody": "Are you sure you want to remove the gear from the activity?", + "modalLabelDeleteGearButton": "Delete gear", + "labelGraph": "Activity data graphs", + "labelDownsampling": "Data downsampled to ~200 points", + "successMessageGearAdded": "Gear added to activity", + "successMessageGearDeleted": "Gear deleted from activity" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/activities/activitySummaryComponent.json b/frontend/src/i18n/en/components/activities/activitySummaryComponent.json new file mode 100644 index 000000000..20e08c5ea --- /dev/null +++ b/frontend/src/i18n/en/components/activities/activitySummaryComponent.json @@ -0,0 +1,14 @@ +{ + "buttonDeleteActivity": "Delete Activity", + "modalDeleteBody1": "Are you sure you want to delete activity ", + "modalDeleteBody2": "This action cannot be undone.", + "activityDistance": "Distance", + "activityTime": "Time", + "activityElevationGain": "Elevation Gain", + "activityPace": "Pace", + "activityAvgPower": "Avg Power", + "activityAvgSpeed": "Avg Speed", + "activityEleGain": "Elevation gain", + "activityEleLoss": "Elevation loss", + "activityNoData": "No data" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/activities/userDistanceStatsComponent.json b/frontend/src/i18n/en/components/activities/userDistanceStatsComponent.json new file mode 100644 index 000000000..def69c6d5 --- /dev/null +++ b/frontend/src/i18n/en/components/activities/userDistanceStatsComponent.json @@ -0,0 +1,7 @@ +{ + "thisMonthDistancesTitle" :"This month distances", + "thisWeekDistancesTitle" :"This week distances", + "distancesRun" :"Run", + "distancesBike" :"Bike", + "distancesSwim" :"Swim" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/followers/followersListComponent.json b/frontend/src/i18n/en/components/followers/followersListComponent.json new file mode 100644 index 000000000..5c8edaa1b --- /dev/null +++ b/frontend/src/i18n/en/components/followers/followersListComponent.json @@ -0,0 +1,12 @@ +{ + "requestAccepted": "Accepted", + "requestPending": "Request pending", + "followingModalTitle": "Delete following", + "followingModalBody": "Are you sure you want to delete following user ", + "followerModalTitle": "Delete follower", + "followerModalBody": "Are you sure you want to delete follower user ", + "followerAcceptModalTitle": "Accept user request", + "followerAcceptModalBody": "Are you sure you want to accept follow request from user ", + "followerDeclineModalTitle": "Decline user request ", + "followerDeclineModalBody": "Are you sure you want to decline follow request from user " +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/footerComponent.json b/frontend/src/i18n/en/components/footerComponent.json new file mode 100644 index 000000000..3cd198212 --- /dev/null +++ b/frontend/src/i18n/en/components/footerComponent.json @@ -0,0 +1,7 @@ +{ + "searchSelectLabel": "Search", + "searchSelectOptionActivity": "Activity", + "searchSelectOptionUser": "User", + "searchSelectOptionGear": "Gear", + "searchInputPlaceholder": "Search text" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/navbarComponent.json b/frontend/src/i18n/en/components/navbarComponent.json new file mode 100644 index 000000000..43df8449c --- /dev/null +++ b/frontend/src/i18n/en/components/navbarComponent.json @@ -0,0 +1,8 @@ +{ + "gear": "Gear", + "profile": "Profile", + "settings": "Settings", + "login": "Login", + "logout": "Logout", + "warningZone": "Alpha software, some features might not work has expected" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/noItemsFoundComponent.json b/frontend/src/i18n/en/components/noItemsFoundComponent.json new file mode 100644 index 000000000..c2abc6c54 --- /dev/null +++ b/frontend/src/i18n/en/components/noItemsFoundComponent.json @@ -0,0 +1,4 @@ +{ + "title" :"Ops...", + "subtitle" :"No records found" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/settings/settingsIntegrationsZoneComponent.json b/frontend/src/i18n/en/components/settings/settingsIntegrationsZoneComponent.json new file mode 100644 index 000000000..c0bc80852 --- /dev/null +++ b/frontend/src/i18n/en/components/settings/settingsIntegrationsZoneComponent.json @@ -0,0 +1,14 @@ +{ + "stravaIntegrationTitle": "Strava", + "stravaIntegrationBody": "Strava is an American internet service for tracking physical exercise which incorporates social network features.", + "buttonConnect": "Connect", + "buttonStravaRetrieveLastWeekActivities": "Retrieve last week activities", + "buttonStravaRetrieveGear": "Retrieve gear", + "errorMessageUnableToLinkStrava": "Unable to link Strava account", + "successMessageStravaAccountLinked": "Strava account linked", + "errorMessageUnableToUnSetStravaState": "Unable to unset Strava state", + "errorMessageUnableToGetStravaActivities": "Unable to get Strava activities", + "errorMessageUnableToGetStravaGear": "Unable to get Strava gear", + "loadingMessageRetrievingStravaActivities": "Retrieving Strava activities", + "loadingMessageRetrievingStravaGear": "Retrieving Strava gear" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/settings/settingsSecurityZoneComponent.json b/frontend/src/i18n/en/components/settings/settingsSecurityZoneComponent.json new file mode 100644 index 000000000..aee52f4a5 --- /dev/null +++ b/frontend/src/i18n/en/components/settings/settingsSecurityZoneComponent.json @@ -0,0 +1,3 @@ +{ + "subtitleChangePassword": "Change password" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/settings/settingsSideBarComponent.json b/frontend/src/i18n/en/components/settings/settingsSideBarComponent.json new file mode 100644 index 000000000..dcdad412e --- /dev/null +++ b/frontend/src/i18n/en/components/settings/settingsSideBarComponent.json @@ -0,0 +1,6 @@ +{ + "usersSection": "Users", + "myProfileSection": "My Profile", + "securitySection": "Security", + "integratuionsSection": "Integrations" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/settings/settingsUserProfileZoneComponent.json b/frontend/src/i18n/en/components/settings/settingsUserProfileZoneComponent.json new file mode 100644 index 000000000..14e9bdab0 --- /dev/null +++ b/frontend/src/i18n/en/components/settings/settingsUserProfileZoneComponent.json @@ -0,0 +1,5 @@ +{ + "buttonDeleteProfilePhoto": "Delete photo", + "modalDeleteProfilePhotoBody": "Are you sure you want to delete your profile photo?", + "buttonEditProfile": "Edit profile" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/settings/settingsUsersZone/usersListComponent.json b/frontend/src/i18n/en/components/settings/settingsUsersZone/usersListComponent.json new file mode 100644 index 000000000..104ce5981 --- /dev/null +++ b/frontend/src/i18n/en/components/settings/settingsUsersZone/usersListComponent.json @@ -0,0 +1,33 @@ +{ + "userListAccessTypeLabel": "Access type: ", + "userListAccessTypeOption1": "Regular user", + "userListAccessTypeOption2": "Administrator", + "userListUserIsActiveBadge": "Active", + "userListUserIsInactiveBadge": "Inactive", + "modalChangeUserPasswordTitle": "Change user password", + "modalChangeUserPasswordPasswordRequirementsTitle": "Password requirements includes:", + "modalChangeUserPasswordCharacters": "- 8 characters;", + "modalChangeUserPasswordCapitalLetters": "- 1 capital letter;", + "modalChangeUserPasswordNumbers": "- 1 number;", + "modalChangeUserPasswordSpecialCharacters": "- 1 special character;", + "modalChangeUserPasswordBodyLabel": "Change password for user ", + "modalChangeUserPasswordPasswordLabel": "New password", + "modalChangeUserPasswordPasswordConfirmationLabel": "Confirm new password", + "modalChangeUserPasswordFeedbackLabel": "Password does not meet requirements", + "modalChangeUserPasswordPasswordsDoNotMatchFeedbackLabel": "Passwords do not match", + "userChangePasswordSuccessMessage": "Password changed successfully", + "userChangePasswordErrorMessage": "Error changing password", + "modalEditUserTitle": "Edit user", + "modalEditUserDeleteUserPhotoButton": "Delete photo", + "modalEditUserIsUserActiveLabel": "Is active", + "modalEditUserIsUserActiveOption1": "Yes", + "modalEditUserIsUserActiveOption2": "No", + "userEditSuccessMessage": "User edited successfully", + "userEditErrorMessage": "Error editing user", + "modalDeleteUserTitle": "Delete user", + "modalDeleteUserBody": "Are you sure you want to delete user ", + "userDeleteSuccessMessage": "User deleted successfully", + "userDeleteErrorMessage": "Error deleting user", + "userPhotoDeleteSuccessMessage": "Photo deleted successfully", + "userPhotoDeleteErrorMessage": "Error deleting photo" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/components/settings/settingsUsersZoneComponent.json b/frontend/src/i18n/en/components/settings/settingsUsersZoneComponent.json new file mode 100644 index 000000000..6d546a78e --- /dev/null +++ b/frontend/src/i18n/en/components/settings/settingsUsersZoneComponent.json @@ -0,0 +1,27 @@ +{ + "buttonAddUser": "Add user", + "addUserModalUserPhotoLabel": "User photo", + "addUserModalUsernameLabel": "Username", + "addUserModalUsernamePlaceholder": "Username (max 45 characters)", + "addUserModalNameLabel": "Name", + "addUserModalNamePlaceholder": "Name (max 45 characters)", + "addUserModalEmailLabel": "Email", + "addUserModalEmailPlaceholder": "Email (max 45 characters)", + "addUserModalPasswordLabel": "Password", + "addUserModalPasswordPlaceholder": "Password", + "addUserModalTownLabel": "Town/city", + "addUserModalTownPlaceholder": "Town/city (max 45 characters)", + "addUserModalBirthdayLabel": "Birthday", + "addUserModalGenderLabel": "Gender", + "addUserModalGenderOption1": "Male", + "addUserModalGenderOption2": "Female", + "addUserModalUserPreferedLanguageLabel": "Preferred language", + "addUserModalPreferredLanguageOption1": "English", + "addUserModalUserTypeLabel": "Access type", + "addUserModalUserTypeOption1": "Regular user", + "addUserModalUserTypeOption2": "Administrator", + "labelNumberOfUsers1": "There is a total of ", + "labelNumberOfUsers2": " user(s) (", + "labelNumberOfUsers3": " loaded):", + "successUserAdded": "User added successfully" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/gears/gearView.json b/frontend/src/i18n/en/gears/gearView.json new file mode 100644 index 000000000..543d7d5b5 --- /dev/null +++ b/frontend/src/i18n/en/gears/gearView.json @@ -0,0 +1,14 @@ +{ + "buttonEditGear": "Edit Gear", + "buttonDeleteGear": "Delete Gear", + "modalEditGearIsActiveLabel": "Is Active", + "modalEditGearIsActiveOption1": "Yes", + "modalEditGearIsActiveOption0": "No", + "modalDeleteGearBody1": "Are you sure you want to delete gear", + "modalDeleteGearBody2": "This action cannot be undone.", + "labelDistance": "Distance", + "title": "Gear activities", + "subtitle": "(last 10 activities)", + "labelDate": "Date", + "successGearEdited": "Gear edited successfully" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/gears/gearsView.json b/frontend/src/i18n/en/gears/gearsView.json new file mode 100644 index 000000000..53760d2fd --- /dev/null +++ b/frontend/src/i18n/en/gears/gearsView.json @@ -0,0 +1,29 @@ +{ + "title": "Gear", + "buttonAddGear": "Add Gear", + "subTitleSearchGearByNickname": "Search gear by nickname", + "placeholderSearchGearByNickname": "Nickname", + "buttonSearchGear": "Search Gear", + "modalBrand": "Brand", + "modalModel": "Model", + "modalNickname": "Nickname", + "modalGearTypeLabel": "Gear type", + "modalGearTypeOption1Bike": "Bike", + "modalGearTypeOption2Shoes": "Shoes", + "modalGearTypeOption3Wetsuit": "Wetsuit", + "modalDateLabel": "Created date", + "modalDatePlaceholder": "Date", + "displayUserNumberOfGears1": "There is a total of ", + "displayUserNumberOfGears2": " gear(s) (", + "displayUserNumberOfGears3": " loaded):", + "gearTypeLabel": "Type: ", + "gearTypeOption1": "Bike", + "gearTypeOption2": "Shoes", + "gearTypeOption3": "Wetsuit", + "activeState": "Active", + "inactiveState": "Inactive", + "gearFromStrava": "Strava", + "successGearAdded": "Gear added successfully", + "successGearDeleted": "Gear deleted successfully", + "errorGearNotFound": "Gear not found" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/generalItens.json b/frontend/src/i18n/en/generalItens.json new file mode 100644 index 000000000..59dea5d2b --- /dev/null +++ b/frontend/src/i18n/en/generalItens.json @@ -0,0 +1,9 @@ +{ + "buttonBack": "Back", + "buttonClose": "Close", + "buttonlistAll": "List all", + "requiredField": "Required fields", + "errorFetchingInfo": "Error fetching info", + "errorEditingInfo": "Error editing info", + "errorDeletingInfo": "Error deleting info" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/homeView.json b/frontend/src/i18n/en/homeView.json new file mode 100644 index 000000000..40c298c6d --- /dev/null +++ b/frontend/src/i18n/en/homeView.json @@ -0,0 +1,12 @@ +{ + "title": "Endurain", + "buttonAddActivity": "Add Activity", + "fieldLabelUploadGPXFile": "Upload GPX file", + "radioUserActivities": "My activities", + "radioFollowerActivities": "Followers activities", + "successActivityAdded": "Activity added successfully", + "errorActivityAdded": "Error adding activity", + "errorActivityNotFound": "Activity not found", + "processingActivity": "Processing activity", + "successActivityDeleted": "Activity deleted successfully" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/loginView.json b/frontend/src/i18n/en/loginView.json new file mode 100644 index 000000000..df5d6c1e7 --- /dev/null +++ b/frontend/src/i18n/en/loginView.json @@ -0,0 +1,14 @@ +{ + "sessionExpired": "User session expired", + "error401": "Invalid username or password", + "error403": "You do not have permission to access this resource", + "error500": "It was not possible to connect to the server. Please try again later", + "errorUndefined": "It was not possible to connect to the server. Please try again later", + "subtitle": "Sign-in bellow", + "username": "Username", + "password": "Password", + "neverExpires": "Remember me (do not tick this box if you are using a shared computer)", + "signInButton": "Sign in", + "signUpText": "Looking for signing up?", + "signUpButton": "Sign up" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/notFoundView.json b/frontend/src/i18n/en/notFoundView.json new file mode 100644 index 000000000..674d1811b --- /dev/null +++ b/frontend/src/i18n/en/notFoundView.json @@ -0,0 +1,5 @@ +{ + "title": "Oops! Page not found", + "subTitle": "The page you are looking for does not exist or it was changed.", + "backToHomeButton": "Back to home" +} \ No newline at end of file diff --git a/frontend/src/i18n/en/userView.json b/frontend/src/i18n/en/userView.json new file mode 100644 index 000000000..47da6dede --- /dev/null +++ b/frontend/src/i18n/en/userView.json @@ -0,0 +1,29 @@ +{ + "thisMonthActivitiesNumber": "This month activities", + "userFollowing": "Following", + "userFollowers": "Followers", + "navigationActivities": "Activities", + "navigationFollowing": "Following", + "navigationFollowers": "Followers", + "navigationUserSettings": "User settings", + "navigationFollow": "Follow", + "modalFollowUserTitle": "Follow user", + "modalFollowUserBody": "Are you sure you want to follow user ", + "errorUnableToSendFollow": "Unable to send follow request to user", + "successFollowRequestSent": "Follow request sent", + "navigationRequestSent": "Request sent", + "modalCancelFollowRequestTitle": "Cancel follow request", + "modalCancelFollowRequestBody": "Are you sure you want to cancel follow request for user ", + "errorUnableToCancelFollowRequest": "Unable to cancel follow request for user", + "successFollowRequestCancelled": "Follow request cancelled", + "navigationUnfollow": "Unfollow", + "modalUnfollowUserTitle": "Unfollow user", + "modalUnfollowUserBody": "Are you sure you want to unfollow user ", + "errorUnableToUnfollow": "Unable to unfollow user", + "successUserUnfollowed": "User unfollowed", + "activitiesPaginationWeek0": "This week", + "activitiesPaginationWeek51": "One year ago", + "successFollowingDeleted": "Following deleted", + "successFollowerDeleted": "Follower deleted", + "successFollowerAccepted": "Follower accepted" +} \ No newline at end of file diff --git a/frontend/src/i18n/index.js b/frontend/src/i18n/index.js new file mode 100644 index 000000000..4bb2d0304 --- /dev/null +++ b/frontend/src/i18n/index.js @@ -0,0 +1,66 @@ +import { createI18n } from 'vue-i18n'; + +// Importing translations +// Navbar and footer components +import enNavbarComponent from './en/components/navbarComponent.json'; +import enFooterComponent from './en/components/footerComponent.json'; +// Activities component +import enUserDistanceStatsComponent from './en/components/activities/userDistanceStatsComponent.json'; +import enActivitySummaryComponent from './en/components/activities/activitySummaryComponent.json'; +// Followers component +import enFollowersListComponent from './en/components/followers/followersListComponent.json'; +// Settings components +import enSettingsSideBarComponent from './en/components/settings/settingsSideBarComponent.json'; +import enSettingsUsersZoneComponent from './en/components/settings/settingsUsersZoneComponent.json'; +import enSettingsUserProfileZoneComponent from './en/components/settings/settingsUserProfileZoneComponent.json'; +import enSettingsSecurityZoneComponent from './en/components/settings/settingsSecurityZoneComponent.json'; +import enSettingsIntegrationsZoneComponent from './en/components/settings/settingsIntegrationsZoneComponent.json'; +import enUsersListComponent from './en/components/settings/settingsUsersZone/usersListComponent.json'; +// General components +import enNoItemsFoundComponent from './en/components/noItemsFoundComponent.json'; +// General translations +import enGeneralItens from './en/generalItens.json' +// Views +import enHomeView from './en/homeView.json'; +import enLoginView from './en/loginView.json'; +import enGearsView from './en/gears/gearsView.json'; +import enGearView from './en/gears/gearView.json'; +import enActivityView from './en/activityView.json'; +import enNotFoundView from './en/notFoundView.json'; +import enUserView from './en/userView.json'; + +// Constructing the messages structure +const messages = { + en: { + navbar: enNavbarComponent, + footer: enFooterComponent, + userDistanceStats: enUserDistanceStatsComponent, + activitySummary: enActivitySummaryComponent, + followersListComponent: enFollowersListComponent, + settingsSideBar: enSettingsSideBarComponent, + settingsUsersZone: enSettingsUsersZoneComponent, + settingsUserProfileZone: enSettingsUserProfileZoneComponent, + settingsSecurityZone: enSettingsSecurityZoneComponent, + settingsIntegrationsZone: enSettingsIntegrationsZoneComponent, + usersListComponent: enUsersListComponent, + noItemsFoundComponent: enNoItemsFoundComponent, + generalItens: enGeneralItens, + home: enHomeView, + login: enLoginView, + gears: enGearsView, + gear: enGearView, + activity: enActivityView, + notFound: enNotFoundView, + user: enUserView, + }, +}; + +// Creating the Vue I18n instance +const i18n = createI18n({ + legacy: false, // you must set `false`, to use Composition API + locale: 'en', // Default locale + fallbackLocale: 'en', // Fallback locale + messages, +}); + +export default i18n; \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 000000000..86b72c7a4 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,33 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import "bootstrap/dist/css/bootstrap.min.css" +import 'bootstrap/dist/js/bootstrap.bundle.min.js'; + +import 'leaflet/dist/leaflet.css'; + +import App from './App.vue' +import router from './router' +import i18n from './i18n'; + +/* import the fontawesome core */ +import { library } from '@fortawesome/fontawesome-svg-core'; +/* import font awesome icon component */ +import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; + +/* import icons */ +import { fas } from '@fortawesome/free-solid-svg-icons'; +import { fab } from '@fortawesome/free-brands-svg-icons'; +import { far } from '@fortawesome/free-regular-svg-icons'; + +/* add icons to the library */ +library.add(fas, fab, far); + +const app = createApp(App) + +app.use(createPinia()) +app.component('font-awesome-icon', FontAwesomeIcon) +app.use(router) +app.use(i18n); + +app.mount('#app') \ No newline at end of file diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 000000000..2767c0b34 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,84 @@ +import { createRouter, createWebHistory } from 'vue-router' + +import HomeView from '../views/HomeView.vue' +import LoginView from '../views/LoginView.vue' +import GearsView from '../views/Gears/GearsView.vue' +import GearView from '../views/Gears/GearView.vue' +import ActivityView from '../views/ActivityView.vue' +import UserView from '../views/UserView.vue' +import SettingsView from '../views/SettingsView.vue'; +import NotFoundView from '../views/NotFoundView.vue'; + +import { auth } from '@/services/auth'; + +//import { useAuthStore } from '@/stores/auth'; + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView + }, + { + path: '/login', + name: 'login', + component: LoginView + }, + { + path: '/gears', + name: 'gears', + component: GearsView + }, + { + path: '/gear/:id', + name: 'gear', + component: GearView + }, + { + path: '/activity/:id', + name: 'activity', + component: ActivityView + }, + { + path: '/user/:id', + name: 'user', + component: UserView + }, + { + path: '/settings', + name: 'settings', + component: SettingsView + }, + { + path: '/:pathMatch(.*)*', + name: 'not-found', + component: NotFoundView, + }, + ] +}) + +router.beforeEach((to, from, next) => { + const accessToken = localStorage.getItem('accessToken'); + const tokenType = localStorage.getItem('tokenType'); + + if (!accessToken && to.path !== '/login') { + next('/login'); + } else if (accessToken && tokenType) { + if (auth.isTokenValid(accessToken)) { + if (to.path === '/login') { + next('/'); + } else { + next(); + } + } else { + auth.removeLoggedUser(); + next({ path: '/login', query: { sessionExpired: 'true' } }); + } + } else { + next(); + } +}); + +export default router; \ No newline at end of file diff --git a/frontend/src/services/activities.js b/frontend/src/services/activities.js new file mode 100644 index 000000000..8c28f0a78 --- /dev/null +++ b/frontend/src/services/activities.js @@ -0,0 +1,46 @@ +import { fetchGetRequest, fetchPostFileRequest, fetchDeleteRequest, fetchPutRequest } from '@/utils/serviceUtils'; + +export const activities = { + getUserWeekActivities(user_id, week_number) { + return fetchGetRequest(`activities/user/${user_id}/week/${week_number}`); + }, + getUserThisWeekStats(user_id) { + return fetchGetRequest(`activities/user/${user_id}/thisweek/distances`); + }, + getUserThisMonthStats(user_id) { + return fetchGetRequest(`activities/user/${user_id}/thismonth/distances`); + }, + getUserThisMonthActivitiesNumber(user_id) { + return fetchGetRequest(`activities/user/${user_id}/thismonth/number`); + }, + getUserActivitiesByGearId(user_id, gear_id) { + return fetchGetRequest(`activities/user/${user_id}/gear/${gear_id}`); + }, + getUserNumberOfActivities(user_id) { + return fetchGetRequest(`activities/user/${user_id}/number`); + }, + getUserActivitiesWithPagination(user_id, pageNumber, numRecords) { + return fetchGetRequest(`activities/user/${user_id}/page_number/${pageNumber}/num_records/${numRecords}`); + }, + getUserFollowersActivitiesWithPagination(user_id, pageNumber, numRecords) { + return fetchGetRequest(`activities/user/${user_id}/followed/page_number/${pageNumber}/num_records/${numRecords}`); + }, + getActivityById(activityId) { + return fetchGetRequest(`activities/${activityId}`); + }, + getActivityByName(name) { + return fetchGetRequest(`activities/name/contains/${name}`); + }, + uploadActivityFile(formData) { + return fetchPostFileRequest('activities/create/upload', formData); + }, + addGearToActivity(activityId, gearId) { + return fetchPutRequest(`activities/${activityId}/addgear/${gearId}`); + }, + deleteGearFromActivity(activityId) { + return fetchPutRequest(`activities/${activityId}/deletegear`); + }, + deleteActivity(activityId) { + return fetchDeleteRequest(`activities/${activityId}/delete`); + } +}; \ No newline at end of file diff --git a/frontend/src/services/activityStreams.js b/frontend/src/services/activityStreams.js new file mode 100644 index 000000000..36e8c661f --- /dev/null +++ b/frontend/src/services/activityStreams.js @@ -0,0 +1,10 @@ +import { fetchGetRequest } from '@/utils/serviceUtils'; + +export const activityStreams = { + async getActivitySteamsByActivityId(activityId) { + return fetchGetRequest(`activities/streams/activity_id/${activityId}/all`); + }, + async getActivitySteamByStreamTypeByActivityId(activityId, streamType) { + return fetchGetRequest(`activities/streams/activity_id/${activityId}/stream_type/${streamType}`); + } +}; \ No newline at end of file diff --git a/frontend/src/services/auth.js b/frontend/src/services/auth.js new file mode 100644 index 000000000..b1e23319a --- /dev/null +++ b/frontend/src/services/auth.js @@ -0,0 +1,33 @@ +import { fetchGetRequestTokenAsParameter, fetchPostFormUrlEncoded } from '@/utils/serviceUtils'; +import { useUserStore } from '@/stores/user'; + +export const auth = { + isTokenValid(token) { + if (!token) { + return false; + } + + const payload = JSON.parse(atob(token.split('.')[1])); + const exp = payload.exp; + const currentTime = Math.floor(Date.now() / 1000); + + return exp > currentTime; + }, + storeLoggedUser(token, userMe) { + localStorage.setItem('accessToken', token.access_token); + localStorage.setItem('tokenType', token.token_type); + localStorage.setItem('userMe', JSON.stringify(userMe)); + }, + removeLoggedUser() { + localStorage.clear(); + const userStore = useUserStore(); + userStore.resetStore(); + //this.$router.push('/login'); + }, + getToken(formData) { + return fetchPostFormUrlEncoded('token', formData); + }, + getUserMe(token) { + return fetchGetRequestTokenAsParameter('users/me', token); + }, +}; \ No newline at end of file diff --git a/frontend/src/services/followers.js b/frontend/src/services/followers.js new file mode 100644 index 000000000..251915251 --- /dev/null +++ b/frontend/src/services/followers.js @@ -0,0 +1,34 @@ +import { fetchGetRequest, fetchDeleteRequest, fetchPutRequest, fetchPostRequest } from '@/utils/serviceUtils'; + +export const followers = { + getUserFollowState(user_id, target_user_id) { + return fetchGetRequest(`followers/user/${user_id}/targetUser/${target_user_id}`); + }, + getUserFollowersAll(user_id) { + return fetchGetRequest(`followers/user/${user_id}/followers/all`); + }, + getUserFollowersCountAll(user_id) { + return fetchGetRequest(`followers/user/${user_id}/followers/count/all`); + }, + getUserFollowersCountAccepted(user_id) { + return fetchGetRequest(`followers/user/${user_id}/followers/count/accepted`); + }, + getUserFollowingAll(user_id) { + return fetchGetRequest(`followers/user/${user_id}/following/all`); + }, + getUserFollowingCountAll(user_id) { + return fetchGetRequest(`followers/user/${user_id}/following/count/all`); + }, + getUserFollowingCountAccepted(user_id) { + return fetchGetRequest(`followers/user/${user_id}/following/count/accepted`); + }, + deleteUserFollowsSpecificUser(user_id, target_user_id) { + return fetchDeleteRequest(`followers/delete/user/${user_id}/targetUser/${target_user_id}`); + }, + createUserFollowsSpecificUser(user_id, target_user_id) { + return fetchPostRequest(`followers/create/user/${user_id}/targetUser/${target_user_id}`); + }, + acceptUserFollowsSpecificUser(user_id, target_user_id) { + return fetchPutRequest(`followers/accept/user/${user_id}/targetUser/${target_user_id}`); + } +}; \ No newline at end of file diff --git a/frontend/src/services/gears.js b/frontend/src/services/gears.js new file mode 100644 index 000000000..4aaf9f7f8 --- /dev/null +++ b/frontend/src/services/gears.js @@ -0,0 +1,28 @@ +import { fetchGetRequest, fetchPostRequest, fetchPutRequest, fetchDeleteRequest } from '@/utils/serviceUtils'; + +export const gears = { + getGearById(gearId) { + return fetchGetRequest(`gear/id/${gearId}`); + }, + getGearFromType(gearType) { + return fetchGetRequest(`gear/type/${gearType}`); + }, + getGearByNickname(nickname) { + return fetchGetRequest(`gear/nickname/${nickname}`); + }, + getUserGearsWithPagination(pageNumber, numRecords) { + return fetchGetRequest(`gear/page_number/${pageNumber}/num_records/${numRecords}`); + }, + getUserGearsNumber() { + return fetchGetRequest('gear/number'); + }, + createGear(data) { + return fetchPostRequest('gear/create', data) + }, + editGear(gearId, data) { + return fetchPutRequest(`gear/${gearId}/edit`, data); + }, + deleteGear(gearId) { + return fetchDeleteRequest(`gear/${gearId}/delete`); + } +}; \ No newline at end of file diff --git a/frontend/src/services/strava.js b/frontend/src/services/strava.js new file mode 100644 index 000000000..374c321c3 --- /dev/null +++ b/frontend/src/services/strava.js @@ -0,0 +1,27 @@ +import { fetchGetRequest, fetchPutRequest } from '@/utils/serviceUtils'; + +export const strava = { + setUniqueUserStateStravaLink(state) { + return fetchPutRequest(`strava/set-user-unique-state/${state}`) + }, + unsetUniqueUserStateStravaLink() { + return fetchPutRequest(`strava/unset-user-unique-state`) + }, + linkStrava(state) { + const stravaClientId = '115321'; + let redirectUri = `${import.meta.env.VITE_BACKEND_PROTOCOL}://${import.meta.env.VITE_BACKEND_HOST}`; + redirectUri = encodeURIComponent(redirectUri); + const scope = 'read,read_all,profile:read_all,activity:read,activity:read_all'; + + const stravaAuthUrl = `http://www.strava.com/oauth/authorize?client_id=${stravaClientId}&response_type=code&redirect_uri=${redirectUri}/strava/link&approval_prompt=force&scope=${scope}&state=${state}`; + + // Redirect to the Strava authorization URL + window.location.href = stravaAuthUrl; + }, + getStravaActivitiesLastDays(days) { + return fetchGetRequest(`strava/activities/days/${days}`); + }, + getStravaGear() { + return fetchGetRequest('strava/gear'); + } +}; \ No newline at end of file diff --git a/frontend/src/services/user.js b/frontend/src/services/user.js new file mode 100644 index 000000000..bfef4f20d --- /dev/null +++ b/frontend/src/services/user.js @@ -0,0 +1,40 @@ +import { fetchGetRequest, fetchPostRequest, fetchPutRequest, fetchDeleteRequest, fetchPostFileRequest } from '@/utils/serviceUtils'; + +export const users = { + getUsersWithPagination(pageNumber, numRecords) { + return fetchGetRequest(`users/all/page_number/${pageNumber}/num_records/${numRecords}`); + }, + getUsersNumber() { + return fetchGetRequest('users/number'); + }, + getUserById(user_id) { + return fetchGetRequest(`users/id/${user_id}`); + }, + getUserByUsername(username){ + return fetchGetRequest(`users/username/contains/${username}`); + }, + createUser(data) { + return fetchPostRequest('users/create', data) + }, + uploadUserImage(data, user_id) { + return fetchPostFileRequest(`users/${user_id}/upload/image`, data); + }, + uploadImage(file, user_id) { + const formData = new FormData(); + formData.append('file', file); + + return users.uploadUserImage(formData, user_id); + }, + editUser(data) { + return fetchPutRequest('users/edit', data) + }, + editUserPassword(data) { + return fetchPutRequest('users/edit/password', data) + }, + deleteUserPhoto(user_id) { + return fetchPutRequest(`users/${user_id}/delete-photo`); + }, + deleteUser(user_id) { + return fetchDeleteRequest(`users/${user_id}/delete`); + } +}; \ No newline at end of file diff --git a/frontend/src/stores/Alerts/errorAlert.js b/frontend/src/stores/Alerts/errorAlert.js new file mode 100644 index 000000000..cd9de66a8 --- /dev/null +++ b/frontend/src/stores/Alerts/errorAlert.js @@ -0,0 +1,16 @@ +import { defineStore } from 'pinia'; + +export const useErrorAlertStore = defineStore('errorAlert', { + state: () => ({ + message: null, + closable: false, + }), + actions: { + setAlertMessage(message) { + this.message = message; + }, + setClosableState(closable) { + this.closable = closable; + } + } +}); \ No newline at end of file diff --git a/frontend/src/stores/Alerts/infoAlert.js b/frontend/src/stores/Alerts/infoAlert.js new file mode 100644 index 000000000..c0d0d267b --- /dev/null +++ b/frontend/src/stores/Alerts/infoAlert.js @@ -0,0 +1,16 @@ +import { defineStore } from 'pinia'; + +export const useInfoAlertStore = defineStore('infoAlert', { + state: () => ({ + message: null, + closable: false, + }), + actions: { + setAlertMessage(message) { + this.message = message; + }, + setClosableState(closable) { + this.closable = closable; + } + } +}); \ No newline at end of file diff --git a/frontend/src/stores/Alerts/loadingAlert.js b/frontend/src/stores/Alerts/loadingAlert.js new file mode 100644 index 000000000..c66d7e27c --- /dev/null +++ b/frontend/src/stores/Alerts/loadingAlert.js @@ -0,0 +1,16 @@ +import { defineStore } from 'pinia'; + +export const useLoadingAlertStore = defineStore('loadingAlert', { + state: () => ({ + message: null, + closable: false, + }), + actions: { + setAlertMessage(message) { + this.message = message; + }, + setClosableState(closable) { + this.closable = closable; + } + } +}); \ No newline at end of file diff --git a/frontend/src/stores/Alerts/successAlert.js b/frontend/src/stores/Alerts/successAlert.js new file mode 100644 index 000000000..c02927700 --- /dev/null +++ b/frontend/src/stores/Alerts/successAlert.js @@ -0,0 +1,16 @@ +import { defineStore } from 'pinia'; + +export const useSuccessAlertStore = defineStore('successAlert', { + state: () => ({ + message: null, + closable: false, + }), + actions: { + setAlertMessage(message) { + this.message = message; + }, + setClosableState(closable) { + this.closable = closable; + } + } +}); \ No newline at end of file diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js new file mode 100644 index 000000000..c73d41a27 --- /dev/null +++ b/frontend/src/stores/auth.js @@ -0,0 +1,53 @@ +import { defineStore } from 'pinia'; + +export const useAuthStore = defineStore('auth', { + state: () => ({ + isLoggedIn: localStorage.getItem('accessToken') ? true : false, + }), + actions: { + /** + * Sets the user as logged in and saves the access token and token type in localStorage. + * @param {Object} data - The data containing the access token and token type. + */ + setUserLoggedIn(data) { + // Set the user as logged in + this.isLoggedIn = true; + + // Save the access token and token type in localStorage + localStorage.setItem('accessToken', data.access_token); + localStorage.setItem('tokenType', data.token_type); + }, + /** + * Sets the user as logged out and clears the localStorage items related to authentication. + */ + setUserLoggedOut() { + // Set the user as logged out + this.isLoggedIn = false; + + // Clear localStorage items related to authentication + localStorage.removeItem('accessToken'); + localStorage.removeItem('tokenType'); + localStorage.removeItem('userMe'); + }, + /** + * Checks if a token is valid by comparing its expiration time with the current time. + * @param {string} token - The token to be checked. + * @returns {boolean} - True if the token is valid, false otherwise. + */ + isTokenValid(token) { + // If there is no token, it is not valid + if (!token) { + return false; + } + + // Decode the token and get the expiration time + const payload = JSON.parse(atob(token.split('.')[1])); + const exp = payload.exp; + // Get the current time in seconds + const currentTime = Math.floor(Date.now() / 1000); + + // Return true if the expiration time is greater than the current time + return exp > currentTime; + }, + }, +}); \ No newline at end of file diff --git a/frontend/src/stores/user.js b/frontend/src/stores/user.js new file mode 100644 index 000000000..5e4d008ba --- /dev/null +++ b/frontend/src/stores/user.js @@ -0,0 +1,228 @@ +import { defineStore } from 'pinia'; +import { activities } from '@/services/activities'; +import { followers } from '@/services/followers'; +import { users } from '@/services/user'; + +// Function to get the default state +const getDefaultState = () => ({ + userMe: null, + thisWeekDistances: null, + thisMonthDistances: null, + thisMonthNumberOfActivities: 0, + userNumberOfActivities: 0, + userActivities: [], + followedUserActivities: [], + userFollowersCountAll: 0, + userFollowersAll: [], + userFollowersCountAccepted: 0, + //userFollowersAccepted: [], + userFollowingCountAll: 0, + userFollowingAll: [], + userFollowingCountAccepted: 0, + //userFollowingAccepted: [], +}); + +export const useUserStore = defineStore('user', { + state: getDefaultState, + actions: { + async fetchUserMe(user_id) { + try { + this.userMe = await users.getUserById(user_id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches the user's statistics for this week and this month. + * @async + * @function fetchUserStats + * @memberof module:stores/user + * @throws {Error} If there is an error fetching the data. + * @returns {Promise} A promise that resolves when the data is fetched successfully. + */ + async fetchUserStats() { + try { + this.thisWeekDistances = await activities.getUserThisWeekStats(this.userMe.id); + this.thisMonthDistances = await activities.getUserThisMonthStats(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches the number of activities for the current user in the current month. + * @async + * @function fetchUserThisMonthActivitiesNumber + * @memberof module:stores/user + * @throws {Error} If there is an error fetching the data. + * @returns {Promise} A promise that resolves when the data is fetched successfully. + */ + async fetchUserThisMonthActivitiesNumber(){ + try { + this.thisMonthNumberOfActivities = await activities.getUserThisMonthActivitiesNumber(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches the number of activities for the user. + * @async + * @function fetchUserActivitiesNumber + * @memberof module:stores/user + * @throws {Error} If there is an error fetching the data. + * @returns {Promise} A promise that resolves when the data is fetched successfully. + */ + async fetchUserActivitiesNumber(){ + try { + this.userNumberOfActivities = await activities.getUserNumberOfActivities(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches user activities with pagination. + * @async + * @function fetchUserActivitiesWithPagination + * @memberof module:stores/user + * @throws {Error} If there is an error fetching the data. + * @param {number} pageNumber - The page number to fetch. + * @param {number} numRecords - The number of records to fetch per page. + * @returns {Promise} - A promise that resolves when the user activities are fetched successfully. + */ + async fetchUserActivitiesWithPagination(pageNumber, numRecords){ + try { + const newActivities = await activities.getUserActivitiesWithPagination(this.userMe.id, pageNumber, numRecords); + + Array.prototype.push.apply(this.userActivities, newActivities); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches the user's followed activities with pagination. + * @async + * @function fetchUserFollowedActivitiesWithPagination + * @memberof module:stores/user + * @throws {Error} If there is an error fetching the data. + * @param {number} pageNumber - The page number to fetch. + * @param {number} numRecords - The number of records to fetch per page. + * @returns {Promise} - A promise that resolves when the data is fetched successfully. + */ + async fetchUserFollowedActivitiesWithPagination(pageNumber, numRecords){ + try { + this.followedUserActivities = await activities.getUserFollowersActivitiesWithPagination(this.userMe.id, pageNumber, numRecords); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches new user activity by activity ID and adds it to the beginning of the user activities array. + * @async + * @function fetchNewUserActivity + * @memberof module:stores/user + * @throws {Error} If there is an error fetching the data. + * @param {string} activityId - The ID of the activity to fetch. + * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. + */ + async fetchNewUserActivity(activityId){ + try { + const newActivity = await activities.getActivityById(activityId); + this.userActivities.unshift(newActivity); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches all followers of the user. + * @async + * @function fetchUserFollowersAll + * @memberof module:stores/user + * @instance + * @throws {Error} If there is an error while fetching the data. + */ + async fetchUserFollowersAll(){ + try { + this.userFollowersAll = await followers.getUserFollowersAll(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches the count of followers for the current user. + * @async + * @function fetchUserFollowersCountAll + * @memberof module:stores/user + * @throws {Error} If there is an error while fetching the data. + * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. + */ + async fetchUserFollowersCountAll(){ + try { + this.userFollowersCountAll = await followers.getUserFollowersCountAll(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches the count of accepted followers for the current user. + * @async + * @function fetchUserFollowersCountAccepted + * @memberof module:stores/user + * @throws {Error} If there is an error while fetching the data. + * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. + */ + async fetchUserFollowersCountAccepted(){ + try { + this.userFollowersCountAccepted = await followers.getUserFollowersCountAccepted(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches all the users that the current user is following. + * @async + * @function fetchUserFollowingAll + * @memberof module:stores/user + * @throws {Error} If there is an error while fetching the data. + */ + async fetchUserFollowingAll(){ + try { + this.userFollowingAll = await followers.getUserFollowingAll(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches the count of users being followed by the current user. + * @async + * @function fetchUserFollowingCountAll + * @memberof module:stores/user + * @throws {Error} If there is an error while fetching the data. + * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. + */ + async fetchUserFollowingCountAll(){ + try { + this.userFollowingCountAll = await followers.getUserFollowingCountAll(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + /** + * Fetches the count of accepted user followings. + * @async + * @function fetchUserFollowingCountAccepted + * @memberof module:stores/user + * @throws {Error} If there is an error while fetching the data. + * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. + */ + async fetchUserFollowingCountAccepted(){ + try { + this.userFollowingCountAccepted = await followers.getUserFollowingCountAccepted(this.userMe.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + // Action to reset the store state + resetStore() { + Object.assign(this.$state, getDefaultState()); + } + } +}); \ No newline at end of file diff --git a/frontend/src/utils/activityUtils.js b/frontend/src/utils/activityUtils.js new file mode 100644 index 000000000..82b126d5e --- /dev/null +++ b/frontend/src/utils/activityUtils.js @@ -0,0 +1,38 @@ +/** + * Formats the pace in minutes per kilometer. + * + * @param {number} pace - The pace in minutes per kilometer. + * @returns {string} The formatted pace in the format "minutes:seconds min/km". + */ +export function formatPace(pace) { + // Convert pace to seconds per kilometer + const pacePerKm = pace * 1000 / 60; + // Calculate minutes and seconds + const minutes = Math.floor(pacePerKm); + const seconds = Math.round((pacePerKm - minutes) * 60); + + // Format the seconds + const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds; + + // Return the formatted pace + return `${minutes}:${formattedSeconds} min/km`; +} + +/** + * Formats the pace for swimming activities. + * @param {number} pace - The pace in minutes per 100 meters. + * @returns {string} The formatted pace in the format "minutes:seconds min/km". + */ +export function formatPaceSwim(pace) { + // Convert pace to seconds per 100 meters + const pacePerKm = pace * 1000 / 60; + // Calculate minutes and seconds + const minutes = Math.floor(pacePerKm); + const seconds = Math.round((pacePerKm - minutes) * 60); + + // Format the seconds + const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds; + + // Return the formatted pace + return `${minutes}:${formattedSeconds} min/km`; +} \ No newline at end of file diff --git a/frontend/src/utils/dateTimeUtils.js b/frontend/src/utils/dateTimeUtils.js new file mode 100644 index 000000000..bf9ada44d --- /dev/null +++ b/frontend/src/utils/dateTimeUtils.js @@ -0,0 +1,55 @@ +/** + * Formats a date string into a localized date format. + * + * @param {string} dateString - The date string to be formatted. + * @returns {string} The formatted date string. + */ +export function formatDate(dateString) { + // Create a new Date object from the date string + const date = new Date(dateString); + + // Return the formatted date string + return date.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: '2-digit' }); +} + + +/** + * Formats a given date string into a time string. + * @param {string} dateString - The date string to be formatted. + * @returns {string} The formatted time string. + */ +export function formatTime(dateString) { + // Create a new Date object from the date string + const date = new Date(dateString); + + // Return the formatted time string + return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); +} + +/** + * Calculates the time difference between two given timestamps. + * + * @param {string} startTime - The start timestamp. + * @param {string} endTime - The end timestamp. + * @returns {string} The formatted time difference. + */ +export function calculateTimeDifference(startTime, endTime) { + // Create new Date objects from the timestamps + const startDateTime = new Date(startTime); + const endDateTime = new Date(endTime); + const interval = new Date(endDateTime - startDateTime); + + // Get the hours, minutes, and seconds from the interval + const hours = interval.getUTCHours(); + const minutes = interval.getUTCMinutes(); + const seconds = interval.getUTCSeconds(); + + // Return the formatted time difference + if (hours < 1) { + // If the difference is less than an hour, return the minutes and seconds + return `${minutes}m ${seconds}s`; + } else { + // If the difference is greater than an hour, return the hours and minutes + return `${hours}h ${minutes}m`; + } +} \ No newline at end of file diff --git a/frontend/src/utils/serviceUtils.js b/frontend/src/utils/serviceUtils.js new file mode 100644 index 000000000..1128ccad2 --- /dev/null +++ b/frontend/src/utils/serviceUtils.js @@ -0,0 +1,195 @@ +const API_URL = `${import.meta.env.VITE_BACKEND_PROTOCOL}://${import.meta.env.VITE_BACKEND_HOST}/`; + +/** + * Makes a GET request to the specified URL with optional headers. + * @param {string} url - The URL to send the GET request to. + * @param {Object} headers - Optional headers to include in the request. + * @returns {Promise} - A promise that resolves to the JSON response from the server. + * @throws {Error} - If the response status is not ok. + */ +export async function fetchGetRequest(url) { + // Create the full URL by combining the API URL with the provided URL + const fullUrl = `${API_URL}${url}`; + // Send the GET request + const response = await fetch(fullUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('accessToken')}`, + }, + }); + // If the response status is not ok, throw an error + if (!response.ok) { + throw new Error('' + response.status); + } + // Return the JSON response + return response.json(); +} + +/** + * Fetches a GET request with a token as a parameter. + * + * @param {string} url - The URL to fetch. + * @param {string} token - The token to include in the request headers. + * @param {Object} headers - Additional headers to include in the request. + * @returns {Promise} - A promise that resolves to the JSON response. + * @throws {Error} - If the response status is not ok. + */ +export async function fetchGetRequestTokenAsParameter(url, token) { + // Check if a token is provided + if (!token) { + throw new Error('No token provided'); + } + // Create the full URL by combining the API URL with the provided URL + const fullUrl = `${API_URL}${url}`; + // Send the GET request + const response = await fetch(fullUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + }); + // If the response status is not ok, throw an error + if (!response.ok) { + throw new Error('' + response.status); + } + // Return the JSON response + return response.json(); +} + +/** + * Sends a POST request with a file using FormData. + * @param {string} url - The URL to send the request to. + * @param {FormData} formData - The FormData object containing the file data. + * @param {Object} headers - Optional headers to include in the request. + * @returns {Promise} - A promise that resolves to the JSON response from the server. + * @throws {Error} - If the response status is not ok. + */ +export async function fetchPostFileRequest(url, formData) { + // Create the full URL by combining the API URL with the provided URL + const fullUrl = `${API_URL}${url}`; + // Send the POST request + const response = await fetch(fullUrl, { + method: 'POST', + body: formData, + headers: { + 'Authorization': `Bearer ${localStorage.getItem('accessToken')}` + }, + }); + // If the response status is not ok, throw an error + if (!response.ok) { + throw new Error('' + response.status); + } + // Return the JSON response + return response.json(); +} + +/** + * Fetches data from the specified URL using the POST method with form-urlencoded data. + * @param {string} url - The URL to fetch data from. + * @param {FormData} formData - The form data to send in the request body. + * @param {Object} headers - Additional headers to include in the request. + * @returns {Promise} - A promise that resolves to the JSON response from the server. + * @throws {Error} - If the response status is not ok. + */ +export async function fetchPostFormUrlEncoded(url, formData) { + // Create the full URL by combining the API URL with the provided URL + const fullUrl = `${API_URL}${url}`; + // Send the POST request + const response = await fetch(fullUrl, { + method: 'POST', + body: formData, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }); + // If the response status is not ok, throw an error + if (!response.ok) { + throw new Error('' + response.status); + } + // Return the JSON response + return response.json(); +} + +/** + * Sends a POST request to the specified URL with the provided data. + * @param {string} url - The URL to send the request to. + * @param {Object} data - The data to include in the request body. + * @param {Object} headers - The optional headers to include in the request. + * @returns {Promise} - A promise that resolves to the JSON response. + * @throws {Error} - If the response status is not ok. + */ +export async function fetchPostRequest(url, data) { + // Create the full URL by combining the API URL with the provided URL + const fullUrl = `${API_URL}${url}`; + // Send the POST request + const response = await fetch(fullUrl, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('accessToken')}`, + }, + }); + // If the response status is not ok, throw an error + if (!response.ok) { + throw new Error('' + response.status); + } + // Return the JSON response + return response.json(); +} + +/** + * Sends a PUT request to the specified URL with the provided data. + * @param {string} url - The URL to send the request to. + * @param {Object} data - The data to be sent in the request body. + * @param {Object} headers - Optional headers to be included in the request. + * @returns {Promise} - A promise that resolves to the JSON response from the server. + * @throws {Error} - If the response status is not ok. + */ +export async function fetchPutRequest(url, data) { + // Create the full URL by combining the API URL with the provided URL + const fullUrl = `${API_URL}${url}`; + // Send the PUT request + const response = await fetch(fullUrl, { + method: 'PUT', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('accessToken')}`, + }, + }); + // If the response status is not ok, throw an error + if (!response.ok) { + throw new Error('' + response.status); + } + // Return the JSON response + return response.json(); +} + +/** + * Sends a DELETE request to the specified URL with optional headers. + * @param {string} url - The URL to send the DELETE request to. + * @param {Object} headers - Optional headers to include in the request. + * @returns {Promise} - A promise that resolves to the JSON response from the server. + * @throws {Error} - If the response status is not ok. + */ +export async function fetchDeleteRequest(url) { + // Create the full URL by combining the API URL with the provided URL + const fullUrl = `${API_URL}${url}`; + // Send the DELETE request + const response = await fetch(fullUrl, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('accessToken')}`, + }, + }); + // If the response status is not ok, throw an error + if (!response.ok) { + throw new Error('' + response.status); + } + // Return the JSON response + return response.json(); +} \ No newline at end of file diff --git a/frontend/src/views/ActivityView.vue b/frontend/src/views/ActivityView.vue new file mode 100644 index 000000000..350894e2a --- /dev/null +++ b/frontend/src/views/ActivityView.vue @@ -0,0 +1,302 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/Gears/GearView.vue b/frontend/src/views/Gears/GearView.vue new file mode 100644 index 000000000..121e5e63a --- /dev/null +++ b/frontend/src/views/Gears/GearView.vue @@ -0,0 +1,326 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/Gears/GearsView.vue b/frontend/src/views/Gears/GearsView.vue new file mode 100644 index 000000000..6ea1c1491 --- /dev/null +++ b/frontend/src/views/Gears/GearsView.vue @@ -0,0 +1,357 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue new file mode 100644 index 000000000..3033f26a0 --- /dev/null +++ b/frontend/src/views/HomeView.vue @@ -0,0 +1,317 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue new file mode 100644 index 000000000..725f58a84 --- /dev/null +++ b/frontend/src/views/LoginView.vue @@ -0,0 +1,138 @@ + + + diff --git a/frontend/src/views/NotFoundView.vue b/frontend/src/views/NotFoundView.vue new file mode 100644 index 000000000..8800fb6da --- /dev/null +++ b/frontend/src/views/NotFoundView.vue @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue new file mode 100644 index 000000000..c60cde974 --- /dev/null +++ b/frontend/src/views/SettingsView.vue @@ -0,0 +1,103 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/UserView.vue b/frontend/src/views/UserView.vue new file mode 100644 index 000000000..2c9193d18 --- /dev/null +++ b/frontend/src/views/UserView.vue @@ -0,0 +1,509 @@ + + + \ No newline at end of file diff --git a/frontend/users/user.php b/frontend/users/user.php deleted file mode 100755 index 42b0b97f8..000000000 --- a/frontend/users/user.php +++ /dev/null @@ -1,960 +0,0 @@ - - - - -
- - - - - - - - - - - - -
-
-
-
- alt="userPicture" class="rounded-circle" width="120" - height="120"> -
-
-

- -

- - - - - - -
-
-
-
-
- - - -

- -

- -
-
- - - - 0 - -
- - - -
-
- - - - 0 - -
- - - -
-
-
-
-
-
- - - - -
-
- - - -
- - km - - 0 km - -
-
- - - -
- - km - - 0 km - -
-
- - - -
- - 10000) { ?> - km - - m - - - 0 m - -
-
- - - - -
-
- - - -
- - km - - 0 km - -
-
- - - -
- - km - - 0 km - -
-
- - - -
- - 10000) { ?> - km - - m - - - 0 m - -
-
-
-
-
- - -
- -
- -
- - - -

- - - - -

- - -
-
-
-
- -
-

-

- -

-
-
-
- - - -
-
-
-
- - - '; - } else { - if ($activity["activity_type"] == 3) { - echo ' (Virtual)'; - } else { - if ($activity["activity_type"] == 4 || $activity["activity_type"] == 5 || $activity["activity_type"] == 6) { - echo ''; - } else { - if ($activity["activity_type"] == 7) { - echo ' (Virtual)'; - } else { - if ($activity["activity_type"] == 8 || $activity["activity_type"] == 9) { - echo ''; - } else { - if ($activity["activity_type"] == 10) { - echo ''; - } - } - } - } - } - } ?> - format("d/m/y"); ?>@ - format("H:i"); ?> - - - - -
-
-
-
- - - -
- - km - - m - -
-
- - - -
- diff($endDateTime); - - if ($interval->h < 1) { - // If the difference is less than one hour - echo $interval->i . "m " . $interval->s . "s"; - } else { - // If the difference is one hour or more - echo $interval->h . "h " . $interval->i . "m"; - } - ?> -
-
- - - - -
- m - - - - - -
- - min/km - - - - - -
- - min/km - - - -
-
-
- -
" id="map_" style="height: 300px">
- - -
- - - - - -
- -
-
- - - -
- -
- -
-
-
-

-

- -

-
-
-
- -
    - - -
  • -
    - alt="userPicture" class="rounded-circle" width="55" height="55"> -
    -
    - "> - - -
    - -
    -
    -
    - - - - - - - - - "> - - - - -
    -
  • - -
- -
- -
- -
-
-
-

-

- -

-
-
-
- - - -
-
- -
-
-
- -
-
- - \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 000000000..60f12f95c --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,16 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +}) \ No newline at end of file diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js new file mode 100644 index 000000000..e82ad0c7d --- /dev/null +++ b/frontend/vitest.config.js @@ -0,0 +1,14 @@ +import { fileURLToPath } from 'node:url' +import { mergeConfig, defineConfig, configDefaults } from 'vitest/config' +import viteConfig from './vite.config' + +export default mergeConfig( + viteConfig, + defineConfig({ + test: { + environment: 'jsdom', + exclude: [...configDefaults.exclude, 'e2e/*'], + root: fileURLToPath(new URL('./', import.meta.url)) + } + }) +) \ No newline at end of file diff --git a/frontend_env.sh b/frontend_env.sh new file mode 100644 index 000000000..47460b5fe --- /dev/null +++ b/frontend_env.sh @@ -0,0 +1,12 @@ +#!/bin/sh +for i in $(env | grep MY_APP_) +do + key=$(echo $i | cut -d '=' -f 1) + value=$(echo $i | cut -d '=' -f 2-) + echo $key=$value + # sed All files + # find /usr/share/nginx/html -type f -exec sed -i "s|${key}|${value}|g" '{}' + + + # sed JS and CSS only + find /usr/share/nginx/html -type f \( -name '*.js' -o -name '*.css' \) -exec sed -i "s|${key}|${value}|g" '{}' + +done \ No newline at end of file diff --git a/nginx-custom.conf b/nginx-custom.conf new file mode 100644 index 000000000..8db0bfd07 --- /dev/null +++ b/nginx-custom.conf @@ -0,0 +1,15 @@ +server { + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html =404; + } + + error_page 500 502 503 504 /50x.html; + + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/requirements.txt b/requirements.txt index 9ddb2bfdd..e2b99c3ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,17 @@ -fastapi==0.108.0 -pydantic==1.10.9 -uvicorn==0.25.0 -python-dotenv==1.0.0 -sqlalchemy==2.0.25 -mysqlclient==2.2.1 +fastapi==0.111.0 +pydantic==1.10.15 +uvicorn==0.29.0 +python-dotenv==1.0.1 +sqlalchemy==2.0.30 +mysqlclient==2.2.4 python-jose[cryptography]==3.3.0 passlib[bcrypt]==1.7.4 apscheduler==3.10.4 -requests==2.31.0 -stravalib==1.5 +requests==2.32.2 +stravalib==1.7 opentelemetry-sdk==1.22.0 opentelemetry-instrumentation-fastapi==0.43b0 opentelemetry.exporter.otlp==1.22.0 -python-multipart==0.0.6 +python-multipart==0.0.9 gpxpy==1.6.2 alembic==1.13.1 \ No newline at end of file