From 849a9b96fe3575adaf93d63a218365bda301f81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 13:36:42 +0400 Subject: [PATCH 001/771] Refactor/upgrade backend and frontend parts (#2) * :recycle: Refactor and simplify backend code * :recycle: Refactor frontend state, integrate typesafe-vuex accessors into state files * :recycle: Use new state accessors and standardize layout * :lock: Upgrade and fix npm security audit * :wrench: Update local re-generation scripts * :loud_sound: Log startup exceptions to detect errors early * :pencil2: Fix password reset token content * :fire: Remove unneeded Dockerfile directives * :fire: Remove unnecessary print * :fire: Remove unnecessary code, upgrade dependencies in backend * :pencil2: Fix typos in docstrings and comments * :building_construction: Improve user Depends utilities to simplify and remove code * :fire: Remove deprecated SQLAlchemy parameter --- dev-fsfp-back.sh | 17 ++ dev-fsfp.sh | 2 + {{cookiecutter.project_slug}}/README.md | 8 +- .../backend/app/app/api/api_v1/api.py | 10 +- .../app/app/api/api_v1/endpoints/token.py | 12 +- .../app/app/api/api_v1/endpoints/user.py | 61 ++--- .../app/app/api/api_v1/endpoints/utils.py | 17 +- .../backend/app/app/api/utils/security.py | 19 +- .../backend/app/app/backend_pre_start.py | 8 +- .../backend/app/app/celeryworker_pre_start.py | 8 +- .../backend/app/app/crud/__init__.py | 1 + .../backend/app/app/crud/user.py | 14 +- .../backend/app/app/db/init_db.py | 8 +- .../backend/app/app/db/session.py | 2 +- .../app/app/tests/api/api_v1/test_user.py | 16 +- .../backend/app/app/tests/crud/test_user.py | 32 +-- .../backend/app/app/tests_pre_start.py | 12 +- .../backend/app/app/utils.py | 2 +- .../backend/app/app/worker.py | 1 - .../backend/app/backend-live.sh | 2 - .../backend/backend.dockerfile | 2 +- .../backend/celeryworker.dockerfile | 2 +- .../backend/tests.dockerfile | 2 +- .../frontend/package-lock.json | 212 +++++++++++------- .../frontend/package.json | 2 +- .../frontend/src/App.vue | 3 +- .../src/components/NotificationsManager.vue | 4 +- .../src/store/admin/accessors/commit.ts | 9 - .../src/store/admin/accessors/dispatch.ts | 10 - .../src/store/admin/accessors/index.ts | 3 - .../src/store/admin/accessors/read.ts | 9 - .../frontend/src/store/admin/actions.ts | 17 +- .../frontend/src/store/admin/getters.ts | 7 + .../frontend/src/store/admin/mutations.ts | 7 + .../src/store/main/accessors/commit.ts | 15 -- .../src/store/main/accessors/dispatch.ts | 20 -- .../src/store/main/accessors/index.ts | 3 - .../frontend/src/store/main/accessors/read.ts | 15 -- .../frontend/src/store/main/actions.ts | 39 ++-- .../frontend/src/store/main/getters.ts | 13 ++ .../frontend/src/store/main/mutations.ts | 13 ++ .../frontend/src/views/Login.vue | 3 +- .../frontend/src/views/PasswordRecovery.vue | 2 +- .../frontend/src/views/ResetPassword.vue | 8 +- .../frontend/src/views/main/Dashboard.vue | 6 +- .../frontend/src/views/main/Main.vue | 11 +- .../frontend/src/views/main/Start.vue | 3 +- .../frontend/src/views/main/admin/Admin.vue | 2 +- .../src/views/main/admin/AdminUsers.vue | 3 +- .../src/views/main/admin/CreateUser.vue | 15 +- .../src/views/main/admin/EditUser.vue | 96 ++++++-- .../src/views/main/profile/UserProfile.vue | 2 +- .../views/main/profile/UserProfileEdit.vue | 41 +++- .../main/profile/UserProfileEditPassword.vue | 12 +- 54 files changed, 492 insertions(+), 371 deletions(-) create mode 100644 dev-fsfp-back.sh delete mode 100644 {{cookiecutter.project_slug}}/backend/app/backend-live.sh delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/commit.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/dispatch.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/index.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/read.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/commit.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/dispatch.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/index.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/read.ts diff --git a/dev-fsfp-back.sh b/dev-fsfp-back.sh new file mode 100644 index 0000000000..4301707bf6 --- /dev/null +++ b/dev-fsfp-back.sh @@ -0,0 +1,17 @@ +#! /usr/bin/env bash + +# Run this script from outside the project, to integrate a dev-fsfp project with changes and review modifications + +# Exit in case of error +set -e + +if [ $(uname -s) = "Linux" ]; then + echo "Remove __pycache__ files" + sudo find ./dev-fsfp/ -type d -name __pycache__ -exec rm -r {} \+ +fi + +rm -rf ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/* + +rsync -a --exclude=node_modules ./dev-fsfp/* ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/ + +rsync -a ./dev-fsfp/{.env,.gitignore,.gitlab-ci.yml} ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/ diff --git a/dev-fsfp.sh b/dev-fsfp.sh index 93c0417537..bb3554d75a 100644 --- a/dev-fsfp.sh +++ b/dev-fsfp.sh @@ -1,5 +1,7 @@ #! /usr/bin/env bash +# Run this script from outside the project, to generate a dev-fsfp project + # Exit in case of error set -e diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 5fdc4799e8..8591a2669c 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -69,7 +69,7 @@ The changes to those files only affect the local development environment, not th For example, the directory with the backend code is mounted as a Docker "host volume" (in the file `docker-compose.dev.volumes.yml`), mapping the code you change live to the directory inside the container. That allows you to test your changes right away, without having to build the Docker image again. It should only be done during development, for production, you should build the Docker image with a recent version of the backend code. But during development, it allows you to iterate very fast. -There is also a commented out `command` override (in the file `docker-compose.dev.command.yml`), if you want to enable it, uncomment it. It makes the backend container run a process that does "nothing", but keeps the process running. That allows you to get inside your living container and run commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detectes changes. +There is also a commented out `command` override (in the file `docker-compose.dev.command.yml`), if you want to enable it, uncomment it. It makes the backend container run a process that does "nothing", but keeps the process running. That allows you to get inside your living container and run commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes. To get inside the container with a `bash` session you can start the stack with: @@ -91,16 +91,16 @@ root@7f2607af31c3:/app# that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory. -There is also a script `backend-live.sh` to run the debug live reloading server. You can run that script from inside the container with: +There is also a script `/start-reload.sh` to run the debug live reloading server. You can run that script from inside the container with: ```bash -bash ./backend-live.sh +bash /start-reload.sh ``` ...it will look like: ```bash -root@7f2607af31c3:/app# bash ./backend-live.sh +root@7f2607af31c3:/app# bash /start-reload.sh ``` and then hit enter. That runs the debugging server that auto reloads when it detects code changes. diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py index 81b645d9cc..8673a99123 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py @@ -1,10 +1,8 @@ from fastapi import APIRouter -from app.api.api_v1.endpoints.token import router as token_router -from app.api.api_v1.endpoints.user import router as user_router -from app.api.api_v1.endpoints.utils import router as utils_router +from app.api.api_v1.endpoints import token, user, utils api_router = APIRouter() -api_router.include_router(token_router) -api_router.include_router(user_router) -api_router.include_router(utils_router) +api_router.include_router(token.router) +api_router.include_router(user.router) +api_router.include_router(utils.router) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py index 402005a492..2640f1c77e 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py @@ -4,12 +4,12 @@ from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session +from app import crud from app.api.utils.db import get_db from app.api.utils.security import get_current_user from app.core import config from app.core.jwt import create_access_token from app.core.security import get_password_hash -from app.crud import user as crud_user from app.db_models.user import User as DBUser from app.models.msg import Msg from app.models.token import Token @@ -30,12 +30,12 @@ def login_access_token( """ OAuth2 compatible token login, get an access token for future requests """ - user = crud_user.authenticate( + user = crud.user.authenticate( db, email=form_data.username, password=form_data.password ) if not user: raise HTTPException(status_code=400, detail="Incorrect email or password") - elif not crud_user.is_active(user): + elif not crud.user.is_active(user): raise HTTPException(status_code=400, detail="Inactive user") access_token_expires = timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES) return { @@ -59,7 +59,7 @@ def recover_password(email: str, db: Session = Depends(get_db)): """ Password Recovery """ - user = crud_user.get_by_email(db, email=email) + user = crud.user.get_by_email(db, email=email) if not user: raise HTTPException( @@ -81,13 +81,13 @@ def reset_password(token: str, new_password: str, db: Session = Depends(get_db)) email = verify_password_reset_token(token) if not email: raise HTTPException(status_code=400, detail="Invalid token") - user = crud_user.get_by_email(db, email=email) + user = crud.user.get_by_email(db, email=email) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system.", ) - elif not crud_user.is_active(user): + elif not crud.user.is_active(user): raise HTTPException(status_code=400, detail="Inactive user") hashed_password = get_password_hash(new_password) user.hashed_password = hashed_password diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py index 31668dc771..a9430ba2eb 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py @@ -5,10 +5,10 @@ from pydantic.types import EmailStr from sqlalchemy.orm import Session +from app import crud from app.api.utils.db import get_db -from app.api.utils.security import get_current_user +from app.api.utils.security import get_current_active_superuser, get_current_active_user from app.core import config -from app.crud import user as crud_user from app.db_models.user import User as DBUser from app.models.user import User, UserInCreate, UserInDB, UserInUpdate from app.utils import send_new_account_email @@ -21,18 +21,12 @@ def read_users( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, - current_user: DBUser = Depends(get_current_user), + current_user: DBUser = Depends(get_current_active_superuser), ): """ Retrieve users """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not crud_user.is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) - users = crud_user.get_multi(db, skip=skip, limit=limit) + users = crud.user.get_multi(db, skip=skip, limit=limit) return users @@ -41,24 +35,18 @@ def create_user( *, db: Session = Depends(get_db), user_in: UserInCreate, - current_user: DBUser = Depends(get_current_user), + current_user: DBUser = Depends(get_current_active_superuser), ): """ Create new user """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not crud_user.is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) - user = crud_user.get_by_email(db, email=user_in.email) + user = crud.user.get_by_email(db, email=user_in.email) if user: raise HTTPException( status_code=400, detail="The user with this username already exists in the system.", ) - user = crud_user.create(db, user_in=user_in) + user = crud.user.create(db, user_in=user_in) if config.EMAILS_ENABLED and user_in.email: send_new_account_email( email_to=user_in.email, username=user_in.email, password=user_in.password @@ -73,13 +61,11 @@ def update_user_me( password: str = Body(None), full_name: str = Body(None), email: EmailStr = Body(None), - current_user: DBUser = Depends(get_current_user), + current_user: DBUser = Depends(get_current_active_user), ): """ Update own user """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") current_user_data = jsonable_encoder(current_user) user_in = UserInUpdate(**current_user_data) if password is not None: @@ -88,19 +74,18 @@ def update_user_me( user_in.full_name = full_name if email is not None: user_in.email = email - user = crud_user.update(db, user=current_user, user_in=user_in) + user = crud.user.update(db, user=current_user, user_in=user_in) return user @router.get("/users/me", tags=["users"], response_model=User) def read_user_me( - db: Session = Depends(get_db), current_user: DBUser = Depends(get_current_user) + db: Session = Depends(get_db), + current_user: DBUser = Depends(get_current_active_user), ): """ Get current user """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") return current_user @@ -120,32 +105,30 @@ def create_user_open( status_code=403, detail="Open user resgistration is forbidden on this server", ) - user = crud_user.get_by_email(db, email=email) + user = crud.user.get_by_email(db, email=email) if user: raise HTTPException( status_code=400, detail="The user with this username already exists in the system", ) user_in = UserInCreate(password=password, email=email, full_name=full_name) - user = crud_user.create(db, user_in=user_in) + user = crud.user.create(db, user_in=user_in) return user @router.get("/users/{user_id}", tags=["users"], response_model=User) def read_user_by_id( user_id: int, - current_user: DBUser = Depends(get_current_user), + current_user: DBUser = Depends(get_current_active_user), db: Session = Depends(get_db), ): """ Get a specific user by username (email) """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - user = crud_user.get(db, user_id=user_id) + user = crud.user.get(db, user_id=user_id) if user == current_user: return user - if not crud_user.is_superuser(current_user): + if not crud.user.is_superuser(current_user): raise HTTPException( status_code=400, detail="The user doesn't have enough privileges" ) @@ -158,23 +141,17 @@ def update_user( db: Session = Depends(get_db), user_id: int, user_in: UserInUpdate, - current_user: UserInDB = Depends(get_current_user), + current_user: UserInDB = Depends(get_current_active_superuser), ): """ Update a user """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not crud_user.is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) - user = crud_user.get(db, user_id=user_id) + user = crud.user.get(db, user_id=user_id) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system", ) - user = crud_user.update(db, user=user, user_in=user_in) + user = crud.user.update(db, user=user, user_in=user_in) return user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py index 874ef5b11e..18995cec1c 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py @@ -1,9 +1,8 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from pydantic.types import EmailStr -from app.api.utils.security import get_current_user +from app.api.utils.security import get_current_active_superuser from app.core.celery_app import celery_app -from app.crud import user as crud_user from app.models.msg import Msg from app.models.user import UserInDB from app.utils import send_test_email @@ -12,22 +11,22 @@ @router.post("/test-celery/", tags=["utils"], response_model=Msg, status_code=201) -def test_celery(msg: Msg, current_user: UserInDB = Depends(get_current_user)): +def test_celery( + msg: Msg, current_user: UserInDB = Depends(get_current_active_superuser) +): """ Test Celery worker """ - if not crud_user.is_superuser(current_user): - raise HTTPException(status_code=400, detail="Not a superuser") celery_app.send_task("app.worker.test_celery", args=[msg.msg]) return {"msg": "Word received"} @router.post("/test-email/", tags=["utils"], response_model=Msg, status_code=201) -def test_email(email_to: EmailStr, current_user: UserInDB = Depends(get_current_user)): +def test_email( + email_to: EmailStr, current_user: UserInDB = Depends(get_current_active_superuser) +): """ Test emails """ - if not crud_user.is_superuser(current_user): - raise HTTPException(status_code=400, detail="Not a superuser") send_test_email(email_to=email_to) return {"msg": "Test email sent"} diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py index e8d16c3766..0e761f7e43 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py @@ -5,10 +5,11 @@ from sqlalchemy.orm import Session from starlette.status import HTTP_403_FORBIDDEN +from app import crud from app.api.utils.db import get_db from app.core import config from app.core.jwt import ALGORITHM -from app.crud import user as crud_user +from app.db_models.user import User from app.models.token import TokenPayload reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/login/access-token") @@ -24,7 +25,21 @@ def get_current_user( raise HTTPException( status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials" ) - user = crud_user.get(db, user_id=token_data.user_id) + user = crud.user.get(db, user_id=token_data.user_id) if not user: raise HTTPException(status_code=404, detail="User not found") return user + + +def get_current_active_user(current_user: User = Security(get_current_user)): + if not crud.user.is_active(current_user): + raise HTTPException(status_code=400, detail="Inactive user") + return current_user + + +def get_current_active_superuser(current_user: User = Security(get_current_user)): + if not crud.user.is_superuser(current_user): + raise HTTPException( + status_code=400, detail="The user doesn't have enough privileges" + ) + return current_user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py b/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py index 41b6b63219..e0bfa489f1 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py @@ -18,8 +18,12 @@ after=after_log(logger, logging.WARN), ) def init(): - # Try to create session to check if DB is awake - db_session.execute("SELECT 1") + try: + # Try to create session to check if DB is awake + db_session.execute("SELECT 1") + except Exception as e: + logger.error(e) + raise e def main(): diff --git a/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py b/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py index 41b6b63219..e0bfa489f1 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py @@ -18,8 +18,12 @@ after=after_log(logger, logging.WARN), ) def init(): - # Try to create session to check if DB is awake - db_session.execute("SELECT 1") + try: + # Try to create session to check if DB is awake + db_session.execute("SELECT 1") + except Exception as e: + logger.error(e) + raise e def main(): diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py index e69de29bb2..f9b61db23e 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py @@ -0,0 +1 @@ +from . import user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py index 938aa6c55a..294ae9ab20 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import List, Optional from fastapi.encoders import jsonable_encoder @@ -7,20 +7,20 @@ from app.models.user import UserInCreate, UserInUpdate -def get(db_session, *, user_id: int) -> Union[User, None]: +def get(db_session, *, user_id: int) -> Optional[User]: return db_session.query(User).filter(User.id == user_id).first() -def get_by_email(db_session, *, email: str) -> Union[User, None]: +def get_by_email(db_session, *, email: str) -> Optional[User]: return db_session.query(User).filter(User.email == email).first() -def authenticate(db_session, *, email: str, password: str) -> Union[User, bool]: +def authenticate(db_session, *, email: str, password: str) -> Optional[User]: user = get_by_email(db_session, email=email) if not user: - return False + return None if not verify_password(password, user.hashed_password): - return False + return None return user @@ -32,7 +32,7 @@ def is_superuser(user) -> bool: return user.is_superuser -def get_multi(db_session, *, skip=0, limit=100) -> Union[List[User], List[None]]: +def get_multi(db_session, *, skip=0, limit=100) -> List[Optional[User]]: return db_session.query(User).offset(skip).limit(limit).all() diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py b/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py index 26e9039651..4b9d825d99 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py @@ -1,19 +1,19 @@ +from app import crud from app.core import config -from app.crud import user as crud_user from app.models.user import UserInCreate def init_db(db_session): # Tables should be created with Alembic migrations # But if you don't want to use migrations, create - # the tables uncommenting the next line + # the tables un-commenting the next line # Base.metadata.create_all(bind=engine) - user = crud_user.get_by_email(db_session, email=config.FIRST_SUPERUSER) + user = crud.user.get_by_email(db_session, email=config.FIRST_SUPERUSER) if not user: user_in = UserInCreate( email=config.FIRST_SUPERUSER, password=config.FIRST_SUPERUSER_PASSWORD, is_superuser=True, ) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db/session.py b/{{cookiecutter.project_slug}}/backend/app/app/db/session.py index 352738e486..63752d1840 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/db/session.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db/session.py @@ -3,7 +3,7 @@ from app.core import config -engine = create_engine(config.SQLALCHEMY_DATABASE_URI, convert_unicode=True) +engine = create_engine(config.SQLALCHEMY_DATABASE_URI) db_session = scoped_session( sessionmaker(autocommit=False, autoflush=False, bind=engine) ) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py index d2ba9fd2e2..cf7b8be062 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py @@ -1,7 +1,7 @@ import requests +from app import crud from app.core import config -from app.crud import user as crud_user from app.db.session import db_session from app.models.user import UserInCreate from app.tests.utils.user import user_authentication_headers @@ -32,7 +32,7 @@ def test_create_user_new_email(superuser_token_headers): ) assert 200 <= r.status_code < 300 created_user = r.json() - user = crud_user.get_by_email(db_session, email=username) + user = crud.user.get_by_email(db_session, email=username) assert user.email == created_user["email"] @@ -41,7 +41,7 @@ def test_get_existing_user(superuser_token_headers): username = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) user_id = user.id r = requests.get( f"{server_api}{config.API_V1_STR}/users/{user_id}", @@ -49,7 +49,7 @@ def test_get_existing_user(superuser_token_headers): ) assert 200 <= r.status_code < 300 api_user = r.json() - user = crud_user.get_by_email(db_session, email=username) + user = crud.user.get_by_email(db_session, email=username) assert user.email == api_user["email"] @@ -59,7 +59,7 @@ def test_create_user_existing_username(superuser_token_headers): # username = email password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) data = {"email": username, "password": password} r = requests.post( f"{server_api}{config.API_V1_STR}/users/", @@ -76,7 +76,7 @@ def test_create_user_by_normal_user(): username = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) user_token_headers = user_authentication_headers(server_api, username, password) data = {"email": username, "password": password} r = requests.post( @@ -90,12 +90,12 @@ def test_retrieve_users(superuser_token_headers): username = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) username2 = random_lower_string() password2 = random_lower_string() user_in2 = UserInCreate(email=username2, password=password2) - user2 = crud_user.create(db_session, user_in=user_in2) + user2 = crud.user.create(db_session, user_in=user_in2) r = requests.get( f"{server_api}{config.API_V1_STR}/users/", headers=superuser_token_headers diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py index b70e0a8159..175e4f4c3e 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py @@ -1,6 +1,6 @@ from fastapi.encoders import jsonable_encoder -from app.crud import user as crud_user +from app import crud from app.db.session import db_session from app.models.user import UserInCreate from app.tests.utils.utils import random_lower_string @@ -10,7 +10,7 @@ def test_create_user(): email = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=email, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) assert user.email == email assert hasattr(user, "hashed_password") @@ -19,8 +19,8 @@ def test_authenticate_user(): email = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=email, password=password) - user = crud_user.create(db_session, user_in=user_in) - authenticated_user = crud_user.authenticate( + user = crud.user.create(db_session, user_in=user_in) + authenticated_user = crud.user.authenticate( db_session, email=email, password=password ) assert authenticated_user @@ -30,16 +30,16 @@ def test_authenticate_user(): def test_not_authenticate_user(): email = random_lower_string() password = random_lower_string() - user = crud_user.authenticate(db_session, email=email, password=password) - assert user is False + user = crud.user.authenticate(db_session, email=email, password=password) + assert user is None def test_check_if_user_is_active(): email = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=email, password=password) - user = crud_user.create(db_session, user_in=user_in) - is_active = crud_user.is_active(user) + user = crud.user.create(db_session, user_in=user_in) + is_active = crud.user.is_active(user) assert is_active is True @@ -48,9 +48,9 @@ def test_check_if_user_is_active_inactive(): password = random_lower_string() user_in = UserInCreate(email=email, password=password, disabled=True) print(user_in) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) print(user) - is_active = crud_user.is_active(user) + is_active = crud.user.is_active(user) print(is_active) assert is_active @@ -59,8 +59,8 @@ def test_check_if_user_is_superuser(): email = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=email, password=password, is_superuser=True) - user = crud_user.create(db_session, user_in=user_in) - is_superuser = crud_user.is_superuser(user) + user = crud.user.create(db_session, user_in=user_in) + is_superuser = crud.user.is_superuser(user) assert is_superuser is True @@ -68,8 +68,8 @@ def test_check_if_user_is_superuser_normal_user(): username = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) - is_superuser = crud_user.is_superuser(user) + user = crud.user.create(db_session, user_in=user_in) + is_superuser = crud.user.is_superuser(user) assert is_superuser is False @@ -77,7 +77,7 @@ def test_get_user(): password = random_lower_string() username = random_lower_string() user_in = UserInCreate(email=username, password=password, is_superuser=True) - user = crud_user.create(db_session, user_in=user_in) - user_2 = crud_user.get(db_session, user_id=user.id) + user = crud.user.create(db_session, user_in=user_in) + user_2 = crud.user.get(db_session, user_id=user.id) assert user.email == user_2.email assert jsonable_encoder(user) == jsonable_encoder(user_2) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py b/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py index 6618668b93..7a2a78b6ec 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py @@ -19,10 +19,14 @@ after=after_log(logger, logging.WARN), ) def init(): - # Try to create session to check if DB is awake - db_session.execute("SELECT 1") - # Wait for API to be awake, run one simple tests to authenticate - test_get_access_token() + try: + # Try to create session to check if DB is awake + db_session.execute("SELECT 1") + # Wait for API to be awake, run one simple tests to authenticate + test_get_access_token() + except Exception as e: + logger.error(e) + raise e def main(): diff --git a/{{cookiecutter.project_slug}}/backend/app/app/utils.py b/{{cookiecutter.project_slug}}/backend/app/app/utils.py index 051891223e..ffd8dc9e8c 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/utils.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/utils.py @@ -71,7 +71,7 @@ def send_reset_password_email(email_to: str, email: str, token: str): def send_new_account_email(email_to: str, username: str, password: str): project_name = config.PROJECT_NAME - subject = f"{project_name} - New acccount for user {username}" + subject = f"{project_name} - New account for user {username}" with open(Path(config.EMAIL_TEMPLATES_DIR) / "new_account.html") as f: template_str = f.read() link = config.SERVER_HOST diff --git a/{{cookiecutter.project_slug}}/backend/app/app/worker.py b/{{cookiecutter.project_slug}}/backend/app/app/worker.py index 2a4a089b5d..82bc5a1ce0 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/worker.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/worker.py @@ -8,5 +8,4 @@ @celery_app.task(acks_late=True) def test_celery(word: str): - print("test task") return f"test task return {word}" diff --git a/{{cookiecutter.project_slug}}/backend/app/backend-live.sh b/{{cookiecutter.project_slug}}/backend/app/backend-live.sh deleted file mode 100644 index c092307cb4..0000000000 --- a/{{cookiecutter.project_slug}}/backend/app/backend-live.sh +++ /dev/null @@ -1,2 +0,0 @@ -#! /usr/bin/env bash -uvicorn app.main:app --host 0.0.0.0 --port 80 --debug diff --git a/{{cookiecutter.project_slug}}/backend/backend.dockerfile b/{{cookiecutter.project_slug}}/backend/backend.dockerfile index 1d102cd70e..1aa82ab18a 100644 --- a/{{cookiecutter.project_slug}}/backend/backend.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/backend.dockerfile @@ -1,6 +1,6 @@ FROM tiangolo/uvicorn-gunicorn-fastapi:python3.6 -RUN pip install celery==4.2.1 passlib[bcrypt] tenacity requests pydantic emails "fastapi>=0.6.0" uvicorn gunicorn pyjwt python-multipart email_validator jinja2 psycopg2-binary alembic SQLAlchemy +RUN pip install celery==4.2.1 passlib[bcrypt] tenacity requests emails "fastapi>=0.7.1" uvicorn gunicorn pyjwt python-multipart email_validator jinja2 psycopg2-binary alembic SQLAlchemy # For development, Jupyter remote kernel, Hydrogen # Using inside the container: diff --git a/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile b/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile index af29ac4d6b..087bf58d1c 100644 --- a/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile @@ -1,6 +1,6 @@ FROM python:3.6 -RUN pip install raven celery==4.2.1 passlib[bcrypt] tenacity requests "fastapi>=0.6.0" emails pyjwt email_validator jinja2 psycopg2-binary alembic SQLAlchemy +RUN pip install raven celery==4.2.1 passlib[bcrypt] tenacity requests "fastapi>=0.7.1" emails pyjwt email_validator jinja2 psycopg2-binary alembic SQLAlchemy # For development, Jupyter remote kernel, Hydrogen # Using inside the container: diff --git a/{{cookiecutter.project_slug}}/backend/tests.dockerfile b/{{cookiecutter.project_slug}}/backend/tests.dockerfile index d2b849f20b..5a9158f122 100644 --- a/{{cookiecutter.project_slug}}/backend/tests.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/tests.dockerfile @@ -1,6 +1,6 @@ FROM python:3.6 -RUN pip install requests pytest tenacity passlib[bcrypt] pydantic "fastapi>=0.6.0" psycopg2-binary SQLAlchemy +RUN pip install requests pytest tenacity passlib[bcrypt] "fastapi>=0.7.1" psycopg2-binary SQLAlchemy # For development, Jupyter remote kernel, Hydrogen # Using inside the container: diff --git a/{{cookiecutter.project_slug}}/frontend/package-lock.json b/{{cookiecutter.project_slug}}/frontend/package-lock.json index b1bd42c0cf..f1934dfd89 100644 --- a/{{cookiecutter.project_slug}}/frontend/package-lock.json +++ b/{{cookiecutter.project_slug}}/frontend/package-lock.json @@ -858,24 +858,12 @@ "integrity": "sha512-ePl4l+7dLLmCucIwgQHAgjiepY++qcI6nb8eAwGNkB6OxmTe3Z9rQU3rSpomqu42PCCnlThZbOoxsf+qylJsLA==", "dev": true }, - "@types/node": { - "version": "10.12.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.20.tgz", - "integrity": "sha512-9spv6SklidqxevvZyOUGjZVz4QRXGu2dNaLyXIFzFYZW0AGDykzPRIUFJXTlQXyfzAucddwTcGtJNim8zqSOPA==", - "dev": true - }, "@types/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz", "integrity": "sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA==", "dev": true }, - "@types/semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", - "dev": true - }, "@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -1034,18 +1022,95 @@ } }, "@vue/cli-plugin-unit-jest": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@vue/cli-plugin-unit-jest/-/cli-plugin-unit-jest-3.3.0.tgz", - "integrity": "sha512-Y/WkrO95vdvjVjeNO1vZRQUAxlZ6ngdgAzvMzCeEaujbRG4b8M6W7ePSAe8C9yfoVcJtbnoHcBv2er31sPwtyQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-unit-jest/-/cli-plugin-unit-jest-3.5.0.tgz", + "integrity": "sha512-JFKiuLil1ayzTZCYk1DgoUFYb0F3nfbdVH3C7CN39EOfNgvEMvtavgS2Pb6MU+xx1f2J71bwVHQYY0HIx8zWJw==", "dev": true, "requires": { - "@vue/cli-shared-utils": "^3.3.0", + "@vue/cli-shared-utils": "^3.5.0", "babel-jest": "^23.6.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "jest": "^23.6.0", "jest-serializer-vue": "^2.0.2", - "jest-transform-stub": "^1.0.0", - "vue-jest": "^3.0.2" + "jest-transform-stub": "^2.0.0", + "vue-jest": "^3.0.3" + }, + "dependencies": { + "@vue/cli-shared-utils": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-3.5.0.tgz", + "integrity": "sha512-+EIwVMTjdfRQVEtcIhpRjNsPB2ZlopiUktlPpx6oLQdlJXwBWkFQVwuXdXHtPYxB5Kzs3VPyUfhHxnPIbNw1+Q==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "execa": "^1.0.0", + "joi": "^14.3.0", + "launch-editor": "^2.2.1", + "lru-cache": "^5.1.1", + "node-ipc": "^9.1.1", + "opn": "^5.3.0", + "ora": "^3.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.7", + "semver": "^5.5.0", + "string.prototype.padstart": "^3.0.0" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cli-spinners": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.0.0.tgz", + "integrity": "sha512-yiEBmhaKPPeBj7wWm4GEdtPZK940p9pl3EANIrnJ3JnvWyrPjcFcsEq6qRUuQ7fzB0+Y82ld3p6B34xo95foWw==", + "dev": true + }, + "ora": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.2.0.tgz", + "integrity": "sha512-XHMZA5WieCbtg+tu0uPF8CjvwQdNzKCX6BVh3N6GFsEXH40mTk5dsw/ya1lBTUGJslcEFJFQ8cBhOgkkZXQtMA==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.0.0", + "wcwidth": "^1.0.1" + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "dev": true, + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "strip-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", + "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, "@vue/cli-service": { @@ -1467,9 +1532,9 @@ }, "dependencies": { "acorn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.6.tgz", - "integrity": "sha512-5M3G/A4uBSMIlfJ+h9W125vJvPFH/zirISsW5qfxF5YzEvXJCtolLoQvM5yZft0DvMcUrPGKPOlgEu55I6iUtA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true } } @@ -3806,15 +3871,15 @@ } }, "cssom": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", - "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", "dev": true }, "cssstyle": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", - "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", + "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", "dev": true, "requires": { "cssom": "0.3.x" @@ -4266,15 +4331,13 @@ } }, "editorconfig": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.2.tgz", - "integrity": "sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ==", + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", "dev": true, "requires": { - "@types/node": "^10.11.7", - "@types/semver": "^5.5.0", "commander": "^2.19.0", - "lru-cache": "^4.1.3", + "lru-cache": "^4.1.5", "semver": "^5.6.0", "sigmund": "^1.0.1" }, @@ -4433,9 +4496,9 @@ "dev": true }, "escodegen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", - "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", "dev": true, "requires": { "esprima": "^3.1.3", @@ -5010,9 +5073,9 @@ } }, "find-babel-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.1.0.tgz", - "integrity": "sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", + "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", "dev": true, "requires": { "json5": "^0.5.1", @@ -5280,14 +5343,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5302,20 +5363,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5432,8 +5490,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5445,7 +5502,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5460,7 +5516,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5468,14 +5523,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5494,7 +5547,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5575,8 +5627,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5588,7 +5639,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5710,7 +5760,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5951,9 +6000,9 @@ "dev": true }, "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { "async": "^2.5.0", @@ -8321,9 +8370,9 @@ } }, "jest-transform-stub": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jest-transform-stub/-/jest-transform-stub-1.0.0.tgz", - "integrity": "sha512-7eilMk4sxi2Fiy223I+BYTS5wJQEGEBqR3D8dy5A6RWmMTnmjipw2ImGDfXzEUBieebyrnitzkJfpNOJSFklLQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jest-transform-stub/-/jest-transform-stub-2.0.0.tgz", + "integrity": "sha512-lspHaCRx/mBbnm3h4uMMS3R5aZzMwyNpNIJLXj4cEsV0mIUtS4IjYJLSoyjRCtnxb6RIGJ4NL2quZzfIeNhbkg==", "dev": true }, "jest-util": { @@ -8394,9 +8443,9 @@ } }, "js-beautify": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.8.9.tgz", - "integrity": "sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.9.0.tgz", + "integrity": "sha512-P0skmY4IDjfLiVrx+GLDeme8w5G0R1IGXgccVU5HP2VM3lRblH7qN2LTea5vZAxrDjpZBD0Jv+ahpjwVcbz/rw==", "dev": true, "requires": { "config-chain": "^1.1.12", @@ -9310,12 +9359,13 @@ } }, "node-notifier": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.3.0.tgz", - "integrity": "sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", "dev": true, "requires": { "growly": "^1.3.0", + "is-wsl": "^1.1.0", "semver": "^5.5.0", "shellwords": "^0.1.1", "which": "^1.3.0" @@ -9404,9 +9454,9 @@ "dev": true }, "nwsapi": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", - "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.1.tgz", + "integrity": "sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg==", "dev": true }, "oauth-sign": { @@ -10891,9 +10941,9 @@ } }, "realpath-native": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.0.2.tgz", - "integrity": "sha512-+S3zTvVt9yTntFrBpm7TQmQ3tzpCrnA1a/y+3cUHAc9ZR6aIjG0WNLR+Rj79QpJktY+VeW/TQtFlQ1bzsehI8g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", "dev": true, "requires": { "util.promisify": "^1.0.0" @@ -13049,9 +13099,9 @@ "dev": true }, "vue-jest": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.2.tgz", - "integrity": "sha512-5XIQ1xQFW0ZnWxHWM7adVA2IqbDsdw1vhgZfGFX4oWd75J38KIS3YT41PtiE7lpMLmNM6+VJ0uprT2mhHjUgkA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.4.tgz", + "integrity": "sha512-PY9Rwt4OyaVlA+KDJJ0614CbEvNOkffDI9g9moLQC/2DDoo0YrqZm7dHi13Q10uoK5Nt5WCYFdeAheOExPah0w==", "dev": true, "requires": { "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", diff --git a/{{cookiecutter.project_slug}}/frontend/package.json b/{{cookiecutter.project_slug}}/frontend/package.json index 3a792b76cb..f1f5232407 100644 --- a/{{cookiecutter.project_slug}}/frontend/package.json +++ b/{{cookiecutter.project_slug}}/frontend/package.json @@ -26,7 +26,7 @@ "@vue/cli-plugin-babel": "^3.3.0", "@vue/cli-plugin-pwa": "^3.3.0", "@vue/cli-plugin-typescript": "^3.3.0", - "@vue/cli-plugin-unit-jest": "^3.3.0", + "@vue/cli-plugin-unit-jest": "^3.5.0", "@vue/cli-service": "^3.3.1", "@vue/test-utils": "^1.0.0-beta.28", "babel-core": "7.0.0-bridge.0", diff --git a/{{cookiecutter.project_slug}}/frontend/src/App.vue b/{{cookiecutter.project_slug}}/frontend/src/App.vue index 01c2c2a9a1..795a97c955 100644 --- a/{{cookiecutter.project_slug}}/frontend/src/App.vue +++ b/{{cookiecutter.project_slug}}/frontend/src/App.vue @@ -21,8 +21,9 @@ + + diff --git a/src/new-frontend/package-lock.json b/src/new-frontend/package-lock.json new file mode 100644 index 0000000000..4a3ba796aa --- /dev/null +++ b/src/new-frontend/package-lock.json @@ -0,0 +1,4510 @@ +{ + "name": "new-frontend", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "new-frontend", + "version": "0.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.53.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.4", + "openapi-typescript-codegen": "0.25.0", + "typescript": "^5.2.2", + "vite": "^5.0.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", + "dev": true, + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", + "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", + "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", + "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", + "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", + "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", + "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", + "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", + "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", + "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", + "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", + "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", + "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", + "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", + "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", + "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", + "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", + "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", + "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", + "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", + "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", + "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", + "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "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.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "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/js": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.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.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", + "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz", + "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz", + "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz", + "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz", + "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz", + "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz", + "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", + "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz", + "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz", + "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz", + "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz", + "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.100.tgz", + "integrity": "sha512-7dKgTyxJjlrMwFZYb1auj3Xq0D8ZBe+5oeIgfMlRU05doXZypYJe0LAk0yjj3WdbwYzpF+T1PLxwTWizI0pckw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.100", + "@swc/core-darwin-x64": "1.3.100", + "@swc/core-linux-arm64-gnu": "1.3.100", + "@swc/core-linux-arm64-musl": "1.3.100", + "@swc/core-linux-x64-gnu": "1.3.100", + "@swc/core-linux-x64-musl": "1.3.100", + "@swc/core-win32-arm64-msvc": "1.3.100", + "@swc/core-win32-ia32-msvc": "1.3.100", + "@swc/core-win32-x64-msvc": "1.3.100" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.100.tgz", + "integrity": "sha512-XVWFsKe6ei+SsDbwmsuRkYck1SXRpO60Hioa4hoLwR8fxbA9eVp6enZtMxzVVMBi8ej5seZ4HZQeAWepbukiBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.100.tgz", + "integrity": "sha512-KF/MXrnH1nakm1wbt4XV8FS7kvqD9TGmVxeJ0U4bbvxXMvzeYUurzg3AJUTXYmXDhH/VXOYJE5N5RkwZZPs5iA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.100.tgz", + "integrity": "sha512-p8hikNnAEJrw5vHCtKiFT4hdlQxk1V7vqPmvUDgL/qe2menQDK/i12tbz7/3BEQ4UqUPnvwpmVn2d19RdEMNxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.100.tgz", + "integrity": "sha512-BWx/0EeY89WC4q3AaIaBSGfQxkYxIlS3mX19dwy2FWJs/O+fMvF9oLk/CyJPOZzbp+1DjGeeoGFuDYpiNO91JA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.100.tgz", + "integrity": "sha512-XUdGu3dxAkjsahLYnm8WijPfKebo+jHgHphDxaW0ovI6sTdmEGFDew7QzKZRlbYL2jRkUuuKuDGvD6lO5frmhA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.100.tgz", + "integrity": "sha512-PhoXKf+f0OaNW/GCuXjJ0/KfK9EJX7z2gko+7nVnEA0p3aaPtbP6cq1Ubbl6CMoPL+Ci3gZ7nYumDqXNc3CtLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.100.tgz", + "integrity": "sha512-PwLADZN6F9cXn4Jw52FeP/MCLVHm8vwouZZSOoOScDtihjY495SSjdPnlosMaRSR4wJQssGwiD/4MbpgQPqbAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.100.tgz", + "integrity": "sha512-0f6nicKSLlDKlyPRl2JEmkpBV4aeDfRQg6n8mPqgL7bliZIcDahG0ej+HxgNjZfS3e0yjDxsNRa6sAqWU2Z60A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.100.tgz", + "integrity": "sha512-b7J0rPoMkRTa3XyUGt8PwCaIBuYWsL2DqbirrQKRESzgCvif5iNpqaM6kjIjI/5y5q1Ycv564CB51YDpiS8EtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.2.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", + "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", + "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", + "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/type-utils": "6.13.1", + "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", + "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", + "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", + "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/utils": "6.13.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", + "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", + "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", + "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", + "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.13.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "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-react-swc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", + "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==", + "dev": true, + "dependencies": { + "@swc/core": "^1.3.96" + }, + "peerDependencies": { + "vite": "^4 || ^5" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "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/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/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/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/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/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/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, + "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/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/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/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "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/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/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "dev": true + }, + "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/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/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/esbuild": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", + "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.8", + "@esbuild/android-arm64": "0.19.8", + "@esbuild/android-x64": "0.19.8", + "@esbuild/darwin-arm64": "0.19.8", + "@esbuild/darwin-x64": "0.19.8", + "@esbuild/freebsd-arm64": "0.19.8", + "@esbuild/freebsd-x64": "0.19.8", + "@esbuild/linux-arm": "0.19.8", + "@esbuild/linux-arm64": "0.19.8", + "@esbuild/linux-ia32": "0.19.8", + "@esbuild/linux-loong64": "0.19.8", + "@esbuild/linux-mips64el": "0.19.8", + "@esbuild/linux-ppc64": "0.19.8", + "@esbuild/linux-riscv64": "0.19.8", + "@esbuild/linux-s390x": "0.19.8", + "@esbuild/linux-x64": "0.19.8", + "@esbuild/netbsd-x64": "0.19.8", + "@esbuild/openbsd-x64": "0.19.8", + "@esbuild/sunos-x64": "0.19.8", + "@esbuild/win32-arm64": "0.19.8", + "@esbuild/win32-ia32": "0.19.8", + "@esbuild/win32-x64": "0.19.8" + } + }, + "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": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", + "@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-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.4.tgz", + "integrity": "sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "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/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/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/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-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "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/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "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/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/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/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "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/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "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/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "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/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-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-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "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/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-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", + "dev": true, + "dependencies": { + "@apidevtools/json-schema-ref-parser": "9.0.9" + }, + "engines": { + "node": ">=10" + } + }, + "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/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "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/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/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.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/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "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/openapi-typescript-codegen": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.25.0.tgz", + "integrity": "sha512-nN/TnIcGbP58qYgwEEy5FrAAjePcYgfMaCe3tsmYyTgI3v4RR9v8os14L+LEWDvV50+CmqiyTzRkKKtJeb6Ybg==", + "dev": true, + "dependencies": { + "camelcase": "^6.3.0", + "commander": "^11.0.0", + "fs-extra": "^11.1.1", + "handlebars": "^4.7.7", + "json-schema-ref-parser": "^9.0.9" + }, + "bin": { + "openapi": "bin/index.js" + } + }, + "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/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-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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/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/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": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "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/rollup": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", + "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.6.1", + "@rollup/rollup-android-arm64": "4.6.1", + "@rollup/rollup-darwin-arm64": "4.6.1", + "@rollup/rollup-darwin-x64": "4.6.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.6.1", + "@rollup/rollup-linux-arm64-gnu": "4.6.1", + "@rollup/rollup-linux-arm64-musl": "4.6.1", + "@rollup/rollup-linux-x64-gnu": "4.6.1", + "@rollup/rollup-linux-x64-musl": "4.6.1", + "@rollup/rollup-win32-arm64-msvc": "4.6.1", + "@rollup/rollup-win32-ia32-msvc": "4.6.1", + "@rollup/rollup-win32-x64-msvc": "4.6.1", + "fsevents": "~2.3.2" + } + }, + "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/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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-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/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/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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "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-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/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.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/vite": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.4.tgz", + "integrity": "sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.31", + "rollup": "^4.2.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/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/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "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/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/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" + } + } + }, + "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, + "@apidevtools/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", + "dev": true, + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "@esbuild/android-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", + "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", + "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", + "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", + "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", + "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", + "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", + "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", + "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", + "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", + "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", + "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", + "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", + "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", + "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", + "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", + "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", + "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", + "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", + "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", + "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", + "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", + "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "dev": true, + "optional": true + }, + "@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, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@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 + }, + "@eslint/eslintrc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "dev": true, + "requires": { + "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" + } + }, + "@eslint/js": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@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 + }, + "@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", + "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz", + "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz", + "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz", + "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz", + "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz", + "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz", + "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", + "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz", + "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz", + "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz", + "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz", + "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==", + "dev": true, + "optional": true + }, + "@swc/core": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.100.tgz", + "integrity": "sha512-7dKgTyxJjlrMwFZYb1auj3Xq0D8ZBe+5oeIgfMlRU05doXZypYJe0LAk0yjj3WdbwYzpF+T1PLxwTWizI0pckw==", + "dev": true, + "requires": { + "@swc/core-darwin-arm64": "1.3.100", + "@swc/core-darwin-x64": "1.3.100", + "@swc/core-linux-arm64-gnu": "1.3.100", + "@swc/core-linux-arm64-musl": "1.3.100", + "@swc/core-linux-x64-gnu": "1.3.100", + "@swc/core-linux-x64-musl": "1.3.100", + "@swc/core-win32-arm64-msvc": "1.3.100", + "@swc/core-win32-ia32-msvc": "1.3.100", + "@swc/core-win32-x64-msvc": "1.3.100", + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + } + }, + "@swc/core-darwin-arm64": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.100.tgz", + "integrity": "sha512-XVWFsKe6ei+SsDbwmsuRkYck1SXRpO60Hioa4hoLwR8fxbA9eVp6enZtMxzVVMBi8ej5seZ4HZQeAWepbukiBw==", + "dev": true, + "optional": true + }, + "@swc/core-darwin-x64": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.100.tgz", + "integrity": "sha512-KF/MXrnH1nakm1wbt4XV8FS7kvqD9TGmVxeJ0U4bbvxXMvzeYUurzg3AJUTXYmXDhH/VXOYJE5N5RkwZZPs5iA==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-gnu": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.100.tgz", + "integrity": "sha512-p8hikNnAEJrw5vHCtKiFT4hdlQxk1V7vqPmvUDgL/qe2menQDK/i12tbz7/3BEQ4UqUPnvwpmVn2d19RdEMNxw==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-musl": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.100.tgz", + "integrity": "sha512-BWx/0EeY89WC4q3AaIaBSGfQxkYxIlS3mX19dwy2FWJs/O+fMvF9oLk/CyJPOZzbp+1DjGeeoGFuDYpiNO91JA==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-gnu": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.100.tgz", + "integrity": "sha512-XUdGu3dxAkjsahLYnm8WijPfKebo+jHgHphDxaW0ovI6sTdmEGFDew7QzKZRlbYL2jRkUuuKuDGvD6lO5frmhA==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-musl": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.100.tgz", + "integrity": "sha512-PhoXKf+f0OaNW/GCuXjJ0/KfK9EJX7z2gko+7nVnEA0p3aaPtbP6cq1Ubbl6CMoPL+Ci3gZ7nYumDqXNc3CtLQ==", + "dev": true, + "optional": true + }, + "@swc/core-win32-arm64-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.100.tgz", + "integrity": "sha512-PwLADZN6F9cXn4Jw52FeP/MCLVHm8vwouZZSOoOScDtihjY495SSjdPnlosMaRSR4wJQssGwiD/4MbpgQPqbAw==", + "dev": true, + "optional": true + }, + "@swc/core-win32-ia32-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.100.tgz", + "integrity": "sha512-0f6nicKSLlDKlyPRl2JEmkpBV4aeDfRQg6n8mPqgL7bliZIcDahG0ej+HxgNjZfS3e0yjDxsNRa6sAqWU2Z60A==", + "dev": true, + "optional": true + }, + "@swc/core-win32-x64-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.100.tgz", + "integrity": "sha512-b7J0rPoMkRTa3XyUGt8PwCaIBuYWsL2DqbirrQKRESzgCvif5iNpqaM6kjIjI/5y5q1Ycv564CB51YDpiS8EtQ==", + "dev": true, + "optional": true + }, + "@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true + }, + "@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true + }, + "@types/react": { + "version": "18.2.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", + "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.2.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", + "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, + "@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", + "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/type-utils": "6.13.1", + "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/parser": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", + "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", + "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1" + } + }, + "@typescript-eslint/type-utils": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", + "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/utils": "6.13.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/types": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", + "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", + "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/utils": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", + "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", + "semver": "^7.5.4" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", + "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.13.1", + "eslint-visitor-keys": "^3.4.1" + } + }, + "@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 + }, + "@vitejs/plugin-react-swc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", + "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==", + "dev": true, + "requires": { + "@swc/core": "^1.3.96" + } + }, + "acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true + }, + "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, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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 + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "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 + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "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, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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 + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "esbuild": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", + "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.19.8", + "@esbuild/android-arm64": "0.19.8", + "@esbuild/android-x64": "0.19.8", + "@esbuild/darwin-arm64": "0.19.8", + "@esbuild/darwin-x64": "0.19.8", + "@esbuild/freebsd-arm64": "0.19.8", + "@esbuild/freebsd-x64": "0.19.8", + "@esbuild/linux-arm": "0.19.8", + "@esbuild/linux-arm64": "0.19.8", + "@esbuild/linux-ia32": "0.19.8", + "@esbuild/linux-loong64": "0.19.8", + "@esbuild/linux-mips64el": "0.19.8", + "@esbuild/linux-ppc64": "0.19.8", + "@esbuild/linux-riscv64": "0.19.8", + "@esbuild/linux-s390x": "0.19.8", + "@esbuild/linux-x64": "0.19.8", + "@esbuild/netbsd-x64": "0.19.8", + "@esbuild/openbsd-x64": "0.19.8", + "@esbuild/sunos-x64": "0.19.8", + "@esbuild/win32-arm64": "0.19.8", + "@esbuild/win32-ia32": "0.19.8", + "@esbuild/win32-x64": "0.19.8" + } + }, + "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 + }, + "eslint": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", + "@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" + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "requires": {} + }, + "eslint-plugin-react-refresh": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.4.tgz", + "integrity": "sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==", + "dev": true, + "requires": {} + }, + "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, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "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 + }, + "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, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "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, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "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 + }, + "fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "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 + }, + "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 + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "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, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "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, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "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 + }, + "ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "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 + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-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 + }, + "json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", + "dev": true, + "requires": { + "@apidevtools/json-schema-ref-parser": "9.0.9" + } + }, + "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 + }, + "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 + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "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, + "requires": { + "json-buffer": "3.0.1" + } + }, + "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, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "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 + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "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 + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "openapi-typescript-codegen": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.25.0.tgz", + "integrity": "sha512-nN/TnIcGbP58qYgwEEy5FrAAjePcYgfMaCe3tsmYyTgI3v4RR9v8os14L+LEWDvV50+CmqiyTzRkKKtJeb6Ybg==", + "dev": true, + "requires": { + "camelcase": "^6.3.0", + "commander": "^11.0.0", + "fs-extra": "^11.1.1", + "handlebars": "^4.7.7", + "json-schema-ref-parser": "^9.0.9" + } + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@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" + } + }, + "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, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "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 + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-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 + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "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 + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", + "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.6.1", + "@rollup/rollup-android-arm64": "4.6.1", + "@rollup/rollup-darwin-arm64": "4.6.1", + "@rollup/rollup-darwin-x64": "4.6.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.6.1", + "@rollup/rollup-linux-arm64-gnu": "4.6.1", + "@rollup/rollup-linux-arm64-musl": "4.6.1", + "@rollup/rollup-linux-x64-gnu": "4.6.1", + "@rollup/rollup-linux-x64-musl": "4.6.1", + "@rollup/rollup-win32-arm64-msvc": "4.6.1", + "@rollup/rollup-win32-ia32-msvc": "4.6.1", + "@rollup/rollup-win32-x64-msvc": "4.6.1", + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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 + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "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 + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "requires": {} + }, + "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, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "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 + }, + "typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "vite": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.4.tgz", + "integrity": "sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==", + "dev": true, + "requires": { + "esbuild": "^0.19.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.31", + "rollup": "^4.2.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "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 + } + } +} diff --git a/src/new-frontend/package.json b/src/new-frontend/package.json new file mode 100644 index 0000000000..9061a5e424 --- /dev/null +++ b/src/new-frontend/package.json @@ -0,0 +1,28 @@ +{ + "name": "new-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.53.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.4", + "typescript": "^5.2.2", + "vite": "^5.0.0" + } +} diff --git a/src/new-frontend/public/vite.svg b/src/new-frontend/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/src/new-frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/new-frontend/src/App.css b/src/new-frontend/src/App.css new file mode 100644 index 0000000000..b9d355df2a --- /dev/null +++ b/src/new-frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/src/new-frontend/src/App.tsx b/src/new-frontend/src/App.tsx new file mode 100644 index 0000000000..6893d8cc2e --- /dev/null +++ b/src/new-frontend/src/App.tsx @@ -0,0 +1,12 @@ +import './App.css' + +function App() { + + + return ( + <> + + ) +} + +export default App diff --git a/src/new-frontend/src/index.css b/src/new-frontend/src/index.css new file mode 100644 index 0000000000..6119ad9a8f --- /dev/null +++ b/src/new-frontend/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/new-frontend/src/main.tsx b/src/new-frontend/src/main.tsx new file mode 100644 index 0000000000..3d7150da80 --- /dev/null +++ b/src/new-frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/new-frontend/src/vite-env.d.ts b/src/new-frontend/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/src/new-frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/new-frontend/tsconfig.json b/src/new-frontend/tsconfig.json new file mode 100644 index 0000000000..a7fc6fbf23 --- /dev/null +++ b/src/new-frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/src/new-frontend/tsconfig.node.json b/src/new-frontend/tsconfig.node.json new file mode 100644 index 0000000000..42872c59f5 --- /dev/null +++ b/src/new-frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/src/new-frontend/vite.config.ts b/src/new-frontend/vite.config.ts new file mode 100644 index 0000000000..861b04b356 --- /dev/null +++ b/src/new-frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) From 40e0e103e53ac2a52afc6a6c6232b036b16b1ae4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 30 Nov 2023 21:16:52 +0000 Subject: [PATCH 139/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e9f898eaec..58c405c9d4 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Set up new frontend with Vite, TypeScript and React. PR [#563](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/563) by [@alejsdev](https://github.com/alejsdev). * 📌 Add NodeJS version management and instructions. PR [#551](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/551) by [@alejsdev](https://github.com/alejsdev). * Add consistent errors for env vars not set. PR [#200](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/200). * Upgrade Traefik to version 2, keeping in sync with DockerSwarm.rocks. PR [#199](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/199). From 9d9581d77d840bfcac0a99e6e496073f30817be0 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 4 Dec 2023 17:09:14 -0500 Subject: [PATCH 140/771] =?UTF-8?q?=F0=9F=90=B3=20Set=20up=20Docker=20conf?= =?UTF-8?q?ig=20for=20new-frontend=20(#564)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.env | 1 + src/docker-compose.override.yml | 12 ++++++++++- src/docker-compose.yml | 12 +++++++++++ src/new-frontend/.dockerignore | 2 ++ src/new-frontend/Dockerfile | 21 +++++++++++++++++++ src/new-frontend/nginx-backend-not-found.conf | 9 ++++++++ src/new-frontend/nginx.conf | 11 ++++++++++ 7 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/new-frontend/.dockerignore create mode 100644 src/new-frontend/Dockerfile create mode 100644 src/new-frontend/nginx-backend-not-found.conf create mode 100644 src/new-frontend/nginx.conf diff --git a/src/.env b/src/.env index c42233208a..9ffb6a7076 100644 --- a/src/.env +++ b/src/.env @@ -13,6 +13,7 @@ TRAEFIK_PUBLIC_TAG=traefik-public DOCKER_IMAGE_BACKEND=backend DOCKER_IMAGE_CELERYWORKER=celery DOCKER_IMAGE_FRONTEND=frontend +DOCKER_IMAGE_NEW_FRONTEND=new-frontend # Backend BACKEND_CORS_ORIGINS="[\"http://localhost\", \"http://localhost:4200\", \"http://localhost:3000\", \"http://localhost:8080\", \"https://localhost\", \"https://localhost:4200\", \"https://localhost:3000\", \"https://localhost:8080\", \"http://local.dockertoolbox.tiangolo.com\", \"http://localhost.tiangolo.com\"]" diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index cb9b60950f..469caed772 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -80,9 +80,19 @@ services: labels: - traefik.enable=true - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) + # - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`old-frontend.localhost.tiangolo.com`) - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 + new-frontend: + build: + context: ./new-frontend + labels: + - traefik.enable=true + - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} + - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) + - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 + networks: traefik-public: # For local dev, don't expect an external Traefik network diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 15b92821ae..7c8d1f4e26 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -190,6 +190,18 @@ services: - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 + + new-frontend: + image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' + build: + context: ./new-frontend + deploy: + labels: + - traefik.enable=true + - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} + - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) + - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 + volumes: app-db-data: diff --git a/src/new-frontend/.dockerignore b/src/new-frontend/.dockerignore new file mode 100644 index 0000000000..76add878f8 --- /dev/null +++ b/src/new-frontend/.dockerignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/src/new-frontend/Dockerfile b/src/new-frontend/Dockerfile new file mode 100644 index 0000000000..c676821937 --- /dev/null +++ b/src/new-frontend/Dockerfile @@ -0,0 +1,21 @@ +# Stage 0, "build-stage", based on Node.js, to build and compile the frontend +FROM node:20 as build-stage + +WORKDIR /app + +COPY package*.json /app/ + +RUN npm install + +COPY ./ /app/ + +RUN npm run build + + +# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx +FROM nginx:1 + +COPY --from=build-stage /app/dist/ /usr/share/nginx/html + +COPY ./nginx.conf /etc/nginx/conf.d/default.conf +COPY ./nginx-backend-not-found.conf /etc/nginx/extra-conf.d/backend-not-found.conf diff --git a/src/new-frontend/nginx-backend-not-found.conf b/src/new-frontend/nginx-backend-not-found.conf new file mode 100644 index 0000000000..f6fea66358 --- /dev/null +++ b/src/new-frontend/nginx-backend-not-found.conf @@ -0,0 +1,9 @@ +location /api { + return 404; +} +location /docs { + return 404; +} +location /redoc { + return 404; +} diff --git a/src/new-frontend/nginx.conf b/src/new-frontend/nginx.conf new file mode 100644 index 0000000000..ed11d3aa19 --- /dev/null +++ b/src/new-frontend/nginx.conf @@ -0,0 +1,11 @@ +server { + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html =404; + } + + include /etc/nginx/extra-conf.d/*.conf; +} From 407e88df20eff0354f3e149487dbb033de25a417 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 4 Dec 2023 22:09:34 +0000 Subject: [PATCH 141/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 58c405c9d4..14accdf891 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* 🐳 Set up Docker config for new-frontend. PR [#564](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/564) by [@alejsdev](https://github.com/alejsdev). * ✨ Set up new frontend with Vite, TypeScript and React. PR [#563](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/563) by [@alejsdev](https://github.com/alejsdev). * 📌 Add NodeJS version management and instructions. PR [#551](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/551) by [@alejsdev](https://github.com/alejsdev). * Add consistent errors for env vars not set. PR [#200](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/200). From 18def07f508dc34ccf2b9f5c6b03b17408ec5fc1 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Tue, 26 Dec 2023 10:51:46 -0500 Subject: [PATCH 142/771] =?UTF-8?q?=E2=9C=A8=20Add=20client=20in=20fronten?= =?UTF-8?q?d=20and=20client=20generation=20(#569)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/app/app/main.py | 10 +- src/new-frontend/README.md | 35 +- .../modify-openapi-operationids.js | 29 ++ src/new-frontend/package-lock.json | 184 ++++++++++ src/new-frontend/package.json | 7 +- src/new-frontend/src/client/core/ApiError.ts | 25 ++ .../src/client/core/ApiRequestOptions.ts | 17 + src/new-frontend/src/client/core/ApiResult.ts | 11 + .../src/client/core/CancelablePromise.ts | 131 +++++++ src/new-frontend/src/client/core/OpenAPI.ts | 32 ++ src/new-frontend/src/client/core/request.ts | 319 ++++++++++++++++++ src/new-frontend/src/client/index.ts | 27 ++ .../models/Body_login_login_access_token.ts | 13 + .../models/Body_login_reset_password.ts | 9 + .../src/client/models/HTTPValidationError.ts | 10 + .../src/client/models/ItemCreate.ts | 9 + src/new-frontend/src/client/models/ItemOut.ts | 10 + .../src/client/models/ItemUpdate.ts | 9 + src/new-frontend/src/client/models/Msg.ts | 8 + src/new-frontend/src/client/models/Token.ts | 9 + src/new-frontend/src/client/models/User.ts | 12 + .../src/client/models/UserCreate.ts | 12 + .../src/client/models/UserCreateOpen.ts | 10 + src/new-frontend/src/client/models/UserOut.ts | 12 + .../src/client/models/ValidationError.ts | 10 + .../src/client/services/ItemsService.ts | 136 ++++++++ .../src/client/services/LoginService.ts | 97 ++++++ .../src/client/services/UsersService.ts | 121 +++++++ .../src/client/services/UtilsService.ts | 58 ++++ 29 files changed, 1346 insertions(+), 26 deletions(-) create mode 100644 src/new-frontend/modify-openapi-operationids.js create mode 100644 src/new-frontend/src/client/core/ApiError.ts create mode 100644 src/new-frontend/src/client/core/ApiRequestOptions.ts create mode 100644 src/new-frontend/src/client/core/ApiResult.ts create mode 100644 src/new-frontend/src/client/core/CancelablePromise.ts create mode 100644 src/new-frontend/src/client/core/OpenAPI.ts create mode 100644 src/new-frontend/src/client/core/request.ts create mode 100644 src/new-frontend/src/client/index.ts create mode 100644 src/new-frontend/src/client/models/Body_login_login_access_token.ts create mode 100644 src/new-frontend/src/client/models/Body_login_reset_password.ts create mode 100644 src/new-frontend/src/client/models/HTTPValidationError.ts create mode 100644 src/new-frontend/src/client/models/ItemCreate.ts create mode 100644 src/new-frontend/src/client/models/ItemOut.ts create mode 100644 src/new-frontend/src/client/models/ItemUpdate.ts create mode 100644 src/new-frontend/src/client/models/Msg.ts create mode 100644 src/new-frontend/src/client/models/Token.ts create mode 100644 src/new-frontend/src/client/models/User.ts create mode 100644 src/new-frontend/src/client/models/UserCreate.ts create mode 100644 src/new-frontend/src/client/models/UserCreateOpen.ts create mode 100644 src/new-frontend/src/client/models/UserOut.ts create mode 100644 src/new-frontend/src/client/models/ValidationError.ts create mode 100644 src/new-frontend/src/client/services/ItemsService.ts create mode 100644 src/new-frontend/src/client/services/LoginService.ts create mode 100644 src/new-frontend/src/client/services/UsersService.ts create mode 100644 src/new-frontend/src/client/services/UtilsService.ts diff --git a/src/backend/app/app/main.py b/src/backend/app/app/main.py index d5d0a79493..006345dab2 100644 --- a/src/backend/app/app/main.py +++ b/src/backend/app/app/main.py @@ -1,11 +1,19 @@ from fastapi import FastAPI +from fastapi.routing import APIRoute from starlette.middleware.cors import CORSMiddleware from app.api.api_v1.api import api_router from app.core.config import settings + +def custom_generate_unique_id(route: APIRoute): + return f"{route.tags[0]}-{route.name}" + + app = FastAPI( - title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json" + title=settings.PROJECT_NAME, + openapi_url=f"{settings.API_V1_STR}/openapi.json", + generate_unique_id_function=custom_generate_unique_id, ) # Set all CORS enabled origins diff --git a/src/new-frontend/README.md b/src/new-frontend/README.md index 0d6babeddb..f22e2e432f 100644 --- a/src/new-frontend/README.md +++ b/src/new-frontend/README.md @@ -1,30 +1,17 @@ -# React + TypeScript + Vite +# Full Stack FastAPI and PostgreSQL - Frontend -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +## Generate Client -Currently, two official plugins are available: +- Start the Docker Compose stack. +- Download the OpenAPI JSON file from `http://localhost/api/v1/openapi.json` and copy it to a new file `openapi.json` next to the `package.json` file. +- To simplify the names in the generated frontend client code, modifying the `openapi.json` file, run: -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +```bash +node modify-openapi-operationids.js +``` -- Configure the top-level `parserOptions` property like this: +- To generate or update the frontend client, run: -```js -export default { - // other rules... - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['./tsconfig.json', './tsconfig.node.json'], - tsconfigRootDir: __dirname, - }, -} +```bash +npm run generate-client ``` - -- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` -- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/src/new-frontend/modify-openapi-operationids.js b/src/new-frontend/modify-openapi-operationids.js new file mode 100644 index 0000000000..18dc38267b --- /dev/null +++ b/src/new-frontend/modify-openapi-operationids.js @@ -0,0 +1,29 @@ +import * as fs from "fs"; + +const filePath = "./openapi.json"; + +fs.readFile(filePath, (err, data) => { + const openapiContent = JSON.parse(data); + if (err) throw err; + + const paths = openapiContent.paths; + + Object.keys(paths).forEach((pathKey) => { + const pathData = paths[pathKey]; + Object.keys(pathData).forEach((method) => { + const operation = pathData[method]; + if (operation.tags && operation.tags.length > 0) { + const tag = operation.tags[0]; + const operationId = operation.operationId; + const toRemove = `${tag}-`; + if (operationId.startsWith(toRemove)) { + const newOperationId = operationId.substring(toRemove.length); + operation.operationId = newOperationId; + } + } + }); + }); + fs.writeFile(filePath, JSON.stringify(openapiContent, null, 2), (err) => { + if (err) throw err; + }); +}); diff --git a/src/new-frontend/package-lock.json b/src/new-frontend/package-lock.json index 4a3ba796aa..88d810869e 100644 --- a/src/new-frontend/package-lock.json +++ b/src/new-frontend/package-lock.json @@ -8,10 +8,13 @@ "name": "new-frontend", "version": "0.0.0", "dependencies": { + "axios": "1.6.2", + "form-data": "4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@types/node": "20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@typescript-eslint/eslint-plugin": "^6.10.0", @@ -883,6 +886,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", @@ -1204,6 +1216,21 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1293,6 +1320,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -1351,6 +1389,14 @@ "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==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1702,6 +1748,38 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -2088,6 +2166,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2320,6 +2417,11 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2644,6 +2746,12 @@ "node": ">=0.8.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -3213,6 +3321,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, "@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", @@ -3418,6 +3535,21 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3486,6 +3618,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -3530,6 +3670,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3797,6 +3942,21 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -4087,6 +4247,19 @@ "picomatch": "^2.3.1" } }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4242,6 +4415,11 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4446,6 +4624,12 @@ "dev": true, "optional": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/src/new-frontend/package.json b/src/new-frontend/package.json index 9061a5e424..c7cfd8b42a 100644 --- a/src/new-frontend/package.json +++ b/src/new-frontend/package.json @@ -7,13 +7,17 @@ "dev": "vite", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "preview": "vite preview", + "generate-client": "openapi --input ./openapi.json --useOptions --useUnionTypes --output ./src/client --client axios" }, "dependencies": { + "axios": "1.6.2", + "form-data": "4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@types/node": "20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@typescript-eslint/eslint-plugin": "^6.10.0", @@ -22,6 +26,7 @@ "eslint": "^8.53.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.4", + "openapi-typescript-codegen": "0.25.0", "typescript": "^5.2.2", "vite": "^5.0.0" } diff --git a/src/new-frontend/src/client/core/ApiError.ts b/src/new-frontend/src/client/core/ApiError.ts new file mode 100644 index 0000000000..d6b8fcc3ad --- /dev/null +++ b/src/new-frontend/src/client/core/ApiError.ts @@ -0,0 +1,25 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: any; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} diff --git a/src/new-frontend/src/client/core/ApiRequestOptions.ts b/src/new-frontend/src/client/core/ApiRequestOptions.ts new file mode 100644 index 0000000000..c19adcc94d --- /dev/null +++ b/src/new-frontend/src/client/core/ApiRequestOptions.ts @@ -0,0 +1,17 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly url: string; + readonly path?: Record; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; +}; diff --git a/src/new-frontend/src/client/core/ApiResult.ts b/src/new-frontend/src/client/core/ApiResult.ts new file mode 100644 index 0000000000..ad8fef2bc3 --- /dev/null +++ b/src/new-frontend/src/client/core/ApiResult.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiResult = { + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; +}; diff --git a/src/new-frontend/src/client/core/CancelablePromise.ts b/src/new-frontend/src/client/core/CancelablePromise.ts new file mode 100644 index 0000000000..55fef85172 --- /dev/null +++ b/src/new-frontend/src/client/core/CancelablePromise.ts @@ -0,0 +1,131 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export class CancelError extends Error { + + constructor(message: string) { + super(message); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + +export class CancelablePromise implements Promise { + #isResolved: boolean; + #isRejected: boolean; + #isCancelled: boolean; + readonly #cancelHandlers: (() => void)[]; + readonly #promise: Promise; + #resolve?: (value: T | PromiseLike) => void; + #reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this.#isResolved = false; + this.#isRejected = false; + this.#isCancelled = false; + this.#cancelHandlers = []; + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + + const onResolve = (value: T | PromiseLike): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isResolved = true; + this.#resolve?.(value); + }; + + const onReject = (reason?: any): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isRejected = true; + this.#reject?.(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this.#isResolved, + }); + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this.#isRejected, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise"; + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike) | null + ): Promise { + return this.#promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: any) => TResult | PromiseLike) | null + ): Promise { + return this.#promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.#promise.finally(onFinally); + } + + public cancel(): void { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isCancelled = true; + if (this.#cancelHandlers.length) { + try { + for (const cancelHandler of this.#cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn('Cancellation threw an error', error); + return; + } + } + this.#cancelHandlers.length = 0; + this.#reject?.(new CancelError('Request aborted')); + } + + public get isCancelled(): boolean { + return this.#isCancelled; + } +} diff --git a/src/new-frontend/src/client/core/OpenAPI.ts b/src/new-frontend/src/client/core/OpenAPI.ts new file mode 100644 index 0000000000..e357bb22c6 --- /dev/null +++ b/src/new-frontend/src/client/core/OpenAPI.ts @@ -0,0 +1,32 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Resolver = (options: ApiRequestOptions) => Promise; +type Headers = Record; + +export type OpenAPIConfig = { + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + HEADERS?: Headers | Resolver | undefined; + ENCODE_PATH?: ((path: string) => string) | undefined; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: '', + VERSION: '0.1.0', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; diff --git a/src/new-frontend/src/client/core/request.ts b/src/new-frontend/src/client/core/request.ts new file mode 100644 index 0000000000..1142d43219 --- /dev/null +++ b/src/new-frontend/src/client/core/request.ts @@ -0,0 +1,319 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import axios from 'axios'; +import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'; +import FormData from 'form-data'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +export const isDefined = (value: T | null | undefined): value is Exclude => { + return value !== undefined && value !== null; +}; + +export const isString = (value: any): value is string => { + return typeof value === 'string'; +}; + +export const isStringWithValue = (value: any): value is string => { + return isString(value) && value !== ''; +}; + +export const isBlob = (value: any): value is Blob => { + return ( + typeof value === 'object' && + typeof value.type === 'string' && + typeof value.stream === 'function' && + typeof value.arrayBuffer === 'function' && + typeof value.constructor === 'function' && + typeof value.constructor.name === 'string' && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); +}; + +export const isFormData = (value: any): value is FormData => { + return value instanceof FormData; +}; + +export const isSuccess = (status: number): boolean => { + return status >= 200 && status < 300; +}; + +export const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; + +export const getQueryString = (params: Record): string => { + const qs: string[] = []; + + const append = (key: string, value: any) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; + + const process = (key: string, value: any) => { + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(v => { + process(key, v); + }); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => { + process(`${key}[${k}]`, v); + }); + } else { + append(key, value); + } + } + }; + + Object.entries(params).forEach(([key, value]) => { + process(key, value); + }); + + if (qs.length > 0) { + return `?${qs.join('&')}`; + } + + return ''; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = `${config.BASE}${path}`; + if (options.query) { + return `${url}${getQueryString(options.query)}`; + } + return url; +}; + +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([_, value]) => isDefined(value)) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver = (options: ApiRequestOptions) => Promise; + +export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +}; + +export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise> => { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const additionalHeaders = await resolve(options, config.HEADERS); + const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {} + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + ...formHeaders, + }) + .filter(([_, value]) => isDefined(value)) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return headers; +}; + +export const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body) { + return options.body; + } + return undefined; +}; + +export const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Record, + onCancel: OnCancel, + axiosClient: AxiosInstance +): Promise> => { + const source = axios.CancelToken.source(); + + const requestConfig: AxiosRequestConfig = { + url, + headers, + data: body ?? formData, + method: options.method, + withCredentials: config.WITH_CREDENTIALS, + cancelToken: source.token, + }; + + onCancel(() => source.cancel('The user aborted a request.')); + + try { + return await axiosClient.request(requestConfig); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + return axiosError.response; + } + throw error; + } +}; + +export const getResponseHeader = (response: AxiosResponse, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader]; + if (isString(content)) { + return content; + } + } + return undefined; +}; + +export const getResponseBody = (response: AxiosResponse): any => { + if (response.status !== 204) { + return response.data; + } + return undefined; +}; + +export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 500: 'Internal Server Error', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? 'unknown'; + const errorStatusText = result.statusText ?? 'unknown'; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError(options, result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @param axiosClient The axios client instance to use + * @returns CancelablePromise + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options, formData); + + if (!onCancel.isCancelled) { + const response = await sendRequest(config, options, url, body, formData, headers, onCancel, axiosClient); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/src/new-frontend/src/client/index.ts b/src/new-frontend/src/client/index.ts new file mode 100644 index 0000000000..98461a0bef --- /dev/null +++ b/src/new-frontend/src/client/index.ts @@ -0,0 +1,27 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export { ApiError } from './core/ApiError'; +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI } from './core/OpenAPI'; +export type { OpenAPIConfig } from './core/OpenAPI'; + +export type { Body_login_login_access_token } from './models/Body_login_login_access_token'; +export type { Body_login_reset_password } from './models/Body_login_reset_password'; +export type { HTTPValidationError } from './models/HTTPValidationError'; +export type { ItemCreate } from './models/ItemCreate'; +export type { ItemOut } from './models/ItemOut'; +export type { ItemUpdate } from './models/ItemUpdate'; +export type { Msg } from './models/Msg'; +export type { Token } from './models/Token'; +export type { User } from './models/User'; +export type { UserCreate } from './models/UserCreate'; +export type { UserCreateOpen } from './models/UserCreateOpen'; +export type { UserOut } from './models/UserOut'; +export type { ValidationError } from './models/ValidationError'; + +export { ItemsService } from './services/ItemsService'; +export { LoginService } from './services/LoginService'; +export { UsersService } from './services/UsersService'; +export { UtilsService } from './services/UtilsService'; diff --git a/src/new-frontend/src/client/models/Body_login_login_access_token.ts b/src/new-frontend/src/client/models/Body_login_login_access_token.ts new file mode 100644 index 0000000000..bc8efd0ea0 --- /dev/null +++ b/src/new-frontend/src/client/models/Body_login_login_access_token.ts @@ -0,0 +1,13 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Body_login_login_access_token = { + grant_type?: string; + username: string; + password: string; + scope?: string; + client_id?: string; + client_secret?: string; +}; diff --git a/src/new-frontend/src/client/models/Body_login_reset_password.ts b/src/new-frontend/src/client/models/Body_login_reset_password.ts new file mode 100644 index 0000000000..0ca32994b5 --- /dev/null +++ b/src/new-frontend/src/client/models/Body_login_reset_password.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Body_login_reset_password = { + token: string; + new_password: string; +}; diff --git a/src/new-frontend/src/client/models/HTTPValidationError.ts b/src/new-frontend/src/client/models/HTTPValidationError.ts new file mode 100644 index 0000000000..e218a9f343 --- /dev/null +++ b/src/new-frontend/src/client/models/HTTPValidationError.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ValidationError } from './ValidationError'; + +export type HTTPValidationError = { + detail?: Array; +}; diff --git a/src/new-frontend/src/client/models/ItemCreate.ts b/src/new-frontend/src/client/models/ItemCreate.ts new file mode 100644 index 0000000000..fbe7c16596 --- /dev/null +++ b/src/new-frontend/src/client/models/ItemCreate.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type ItemCreate = { + title: string; + description?: string; +}; diff --git a/src/new-frontend/src/client/models/ItemOut.ts b/src/new-frontend/src/client/models/ItemOut.ts new file mode 100644 index 0000000000..06787f5e17 --- /dev/null +++ b/src/new-frontend/src/client/models/ItemOut.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type ItemOut = { + title: string; + description?: string; + id: number; +}; diff --git a/src/new-frontend/src/client/models/ItemUpdate.ts b/src/new-frontend/src/client/models/ItemUpdate.ts new file mode 100644 index 0000000000..ef82c3c44e --- /dev/null +++ b/src/new-frontend/src/client/models/ItemUpdate.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type ItemUpdate = { + title?: string; + description?: string; +}; diff --git a/src/new-frontend/src/client/models/Msg.ts b/src/new-frontend/src/client/models/Msg.ts new file mode 100644 index 0000000000..f087de476a --- /dev/null +++ b/src/new-frontend/src/client/models/Msg.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Msg = { + msg: string; +}; diff --git a/src/new-frontend/src/client/models/Token.ts b/src/new-frontend/src/client/models/Token.ts new file mode 100644 index 0000000000..c58838f9a1 --- /dev/null +++ b/src/new-frontend/src/client/models/Token.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Token = { + access_token: string; + token_type: string; +}; diff --git a/src/new-frontend/src/client/models/User.ts b/src/new-frontend/src/client/models/User.ts new file mode 100644 index 0000000000..ff390acb93 --- /dev/null +++ b/src/new-frontend/src/client/models/User.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type User = { + email?: string; + is_active?: boolean; + is_superuser?: boolean; + full_name?: string; + id?: number; +}; diff --git a/src/new-frontend/src/client/models/UserCreate.ts b/src/new-frontend/src/client/models/UserCreate.ts new file mode 100644 index 0000000000..9970feb96f --- /dev/null +++ b/src/new-frontend/src/client/models/UserCreate.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type UserCreate = { + email: string; + is_active?: boolean; + is_superuser?: boolean; + full_name?: string; + password: string; +}; diff --git a/src/new-frontend/src/client/models/UserCreateOpen.ts b/src/new-frontend/src/client/models/UserCreateOpen.ts new file mode 100644 index 0000000000..27ca0f1d2f --- /dev/null +++ b/src/new-frontend/src/client/models/UserCreateOpen.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type UserCreateOpen = { + email: string; + password: string; + full_name?: string; +}; diff --git a/src/new-frontend/src/client/models/UserOut.ts b/src/new-frontend/src/client/models/UserOut.ts new file mode 100644 index 0000000000..9f2faf59a6 --- /dev/null +++ b/src/new-frontend/src/client/models/UserOut.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type UserOut = { + email: string; + is_active?: boolean; + is_superuser?: boolean; + full_name?: string; + id: number; +}; diff --git a/src/new-frontend/src/client/models/ValidationError.ts b/src/new-frontend/src/client/models/ValidationError.ts new file mode 100644 index 0000000000..0a3e90e986 --- /dev/null +++ b/src/new-frontend/src/client/models/ValidationError.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type ValidationError = { + loc: Array<(string | number)>; + msg: string; + type: string; +}; diff --git a/src/new-frontend/src/client/services/ItemsService.ts b/src/new-frontend/src/client/services/ItemsService.ts new file mode 100644 index 0000000000..2f9ed3cad7 --- /dev/null +++ b/src/new-frontend/src/client/services/ItemsService.ts @@ -0,0 +1,136 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ItemCreate } from '../models/ItemCreate'; +import type { ItemOut } from '../models/ItemOut'; +import type { ItemUpdate } from '../models/ItemUpdate'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class ItemsService { + + /** + * Read Items + * Retrieve items. + * @returns ItemOut Successful Response + * @throws ApiError + */ + public static readItems({ +skip, +limit = 100, +}: { +skip?: number, +limit?: number, +}): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/items/', + query: { + 'skip': skip, + 'limit': limit, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Create Item + * Create new item. + * @returns ItemOut Successful Response + * @throws ApiError + */ + public static createItem({ +requestBody, +}: { +requestBody: ItemCreate, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/items/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Read Item + * Get item by ID. + * @returns ItemOut Successful Response + * @throws ApiError + */ + public static readItem({ +id, +}: { +id: number, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/items/{id}', + path: { + 'id': id, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Update Item + * Update an item. + * @returns ItemOut Successful Response + * @throws ApiError + */ + public static updateItem({ +id, +requestBody, +}: { +id: number, +requestBody: ItemUpdate, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/v1/items/{id}', + path: { + 'id': id, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Delete Item + * Delete an item. + * @returns ItemOut Successful Response + * @throws ApiError + */ + public static deleteItem({ +id, +}: { +id: number, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/items/{id}', + path: { + 'id': id, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + +} diff --git a/src/new-frontend/src/client/services/LoginService.ts b/src/new-frontend/src/client/services/LoginService.ts new file mode 100644 index 0000000000..a5fd1270a0 --- /dev/null +++ b/src/new-frontend/src/client/services/LoginService.ts @@ -0,0 +1,97 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Body_login_login_access_token } from '../models/Body_login_login_access_token'; +import type { Body_login_reset_password } from '../models/Body_login_reset_password'; +import type { Msg } from '../models/Msg'; +import type { Token } from '../models/Token'; +import type { User } from '../models/User'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class LoginService { + + /** + * Login Access Token + * OAuth2 compatible token login, get an access token for future requests + * @returns Token Successful Response + * @throws ApiError + */ + public static loginAccessToken({ +formData, +}: { +formData: Body_login_login_access_token, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/login/access-token', + formData: formData, + mediaType: 'application/x-www-form-urlencoded', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Test Token + * Test access token + * @returns User Successful Response + * @throws ApiError + */ + public static testToken(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/login/test-token', + }); + } + + /** + * Recover Password + * Password Recovery + * @returns Msg Successful Response + * @throws ApiError + */ + public static recoverPassword({ +email, +}: { +email: string, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/password-recovery/{email}', + path: { + 'email': email, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Reset Password + * Reset password + * @returns Msg Successful Response + * @throws ApiError + */ + public static resetPassword({ +requestBody, +}: { +requestBody: Body_login_reset_password, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/reset-password/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + +} diff --git a/src/new-frontend/src/client/services/UsersService.ts b/src/new-frontend/src/client/services/UsersService.ts new file mode 100644 index 0000000000..b6cfa1fe2a --- /dev/null +++ b/src/new-frontend/src/client/services/UsersService.ts @@ -0,0 +1,121 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { UserCreate } from '../models/UserCreate'; +import type { UserCreateOpen } from '../models/UserCreateOpen'; +import type { UserOut } from '../models/UserOut'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class UsersService { + + /** + * Read Users + * Retrieve users. + * @returns UserOut Successful Response + * @throws ApiError + */ + public static readUsers({ +skip, +limit = 100, +}: { +skip?: number, +limit?: number, +}): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/users/', + query: { + 'skip': skip, + 'limit': limit, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Create User + * Create new user. + * @returns UserOut Successful Response + * @throws ApiError + */ + public static createUser({ +requestBody, +}: { +requestBody: UserCreate, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/users/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Read User Me + * Get current user. + * @returns UserOut Successful Response + * @throws ApiError + */ + public static readUserMe(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/users/me', + }); + } + + /** + * Create User Open + * Create new user without the need to be logged in. + * @returns UserOut Successful Response + * @throws ApiError + */ + public static createUserOpen({ +requestBody, +}: { +requestBody: UserCreateOpen, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/users/open', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Read User By Id + * Get a specific user by id. + * @returns UserOut Successful Response + * @throws ApiError + */ + public static readUserById({ +userId, +}: { +userId: number, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/users/{user_id}', + path: { + 'user_id': userId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + +} diff --git a/src/new-frontend/src/client/services/UtilsService.ts b/src/new-frontend/src/client/services/UtilsService.ts new file mode 100644 index 0000000000..7b34b71112 --- /dev/null +++ b/src/new-frontend/src/client/services/UtilsService.ts @@ -0,0 +1,58 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Msg } from '../models/Msg'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class UtilsService { + + /** + * Test Celery + * Test Celery worker. + * @returns Msg Successful Response + * @throws ApiError + */ + public static testCelery({ +requestBody, +}: { +requestBody: Msg, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/utils/test-celery/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + + /** + * Test Email + * Test emails. + * @returns Msg Successful Response + * @throws ApiError + */ + public static testEmail({ +emailTo, +}: { +emailTo: string, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/utils/test-email/', + query: { + 'email_to': emailTo, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + +} From 95b5c8c2fb27a31e08cfd102ddb100a98830bdbf Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 26 Dec 2023 15:52:06 +0000 Subject: [PATCH 143/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 14accdf891..2db4d2fd6f 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add client in frontend and client generation. PR [#569](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/569) by [@alejsdev](https://github.com/alejsdev). * 🐳 Set up Docker config for new-frontend. PR [#564](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/564) by [@alejsdev](https://github.com/alejsdev). * ✨ Set up new frontend with Vite, TypeScript and React. PR [#563](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/563) by [@alejsdev](https://github.com/alejsdev). * 📌 Add NodeJS version management and instructions. PR [#551](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/551) by [@alejsdev](https://github.com/alejsdev). From 9d45c25e86c1b40a42b169c3ccdd06275402b008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 27 Dec 2023 19:06:47 +0100 Subject: [PATCH 144/771] =?UTF-8?q?=E2=9C=A8=20Update=20code=20for=20login?= =?UTF-8?q?=20API=20(#571)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Update models for login API * ✨ Add authenticate simplified CRUD * ♻️ Refactor get_current_user dependency, integrate is_active * ✨ Refactor and upgrade login API code --- .../app/app/api/api_v1/endpoints/login.py | 66 +++++++++---------- src/backend/app/app/api/deps.py | 12 +--- src/backend/app/app/crud/__init__.py | 11 +++- src/backend/app/app/models.py | 10 ++- 4 files changed, 52 insertions(+), 47 deletions(-) diff --git a/src/backend/app/app/api/api_v1/endpoints/login.py b/src/backend/app/app/api/api_v1/endpoints/login.py index 4dc3a9b248..15f0f3f095 100644 --- a/src/backend/app/app/api/api_v1/endpoints/login.py +++ b/src/backend/app/app/api/api_v1/endpoints/login.py @@ -1,15 +1,15 @@ from datetime import timedelta -from typing import Any +from typing import Annotated, Any -from fastapi import APIRouter, Body, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException from fastapi.security import OAuth2PasswordRequestForm -from sqlalchemy.orm import Session -from app import crud, models, schemas -from app.api import deps +from app import crud +from app.api.deps import CurrentUser, SessionDep from app.core import security from app.core.config import settings from app.core.security import get_password_hash +from app.models import Message, NewPassword, Token, UserOut from app.utils import ( generate_password_reset_token, send_reset_password_email, @@ -19,43 +19,42 @@ router = APIRouter() -@router.post("/login/access-token", response_model=schemas.Token) +@router.post("/login/access-token") def login_access_token( - db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends() -) -> Any: + session: SessionDep, form_data: Annotated[OAuth2PasswordRequestForm, Depends()] +) -> Token: """ OAuth2 compatible token login, get an access token for future requests """ - user = crud.user.authenticate( - db, email=form_data.username, password=form_data.password + user = crud.authenticate( + session=session, email=form_data.username, password=form_data.password ) if not user: raise HTTPException(status_code=400, detail="Incorrect email or password") - elif not crud.user.is_active(user): + elif not user.is_active: raise HTTPException(status_code=400, detail="Inactive user") access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) - return { - "access_token": security.create_access_token( + return Token( + access_token=security.create_access_token( user.id, expires_delta=access_token_expires - ), - "token_type": "bearer", - } + ) + ) -@router.post("/login/test-token", response_model=schemas.User) -def test_token(current_user: models.User = Depends(deps.get_current_user)) -> Any: +@router.post("/login/test-token", response_model=UserOut) +def test_token(current_user: CurrentUser) -> Any: """ Test access token """ return current_user -@router.post("/password-recovery/{email}", response_model=schemas.Msg) -def recover_password(email: str, db: Session = Depends(deps.get_db)) -> Any: +@router.post("/password-recovery/{email}") +def recover_password(email: str, session: SessionDep) -> Message: """ Password Recovery """ - user = crud.user.get_by_email(db, email=email) + user = crud.get_user_by_email(session=session, email=email) if not user: raise HTTPException( @@ -66,31 +65,30 @@ def recover_password(email: str, db: Session = Depends(deps.get_db)) -> Any: send_reset_password_email( email_to=user.email, email=email, token=password_reset_token ) - return {"msg": "Password recovery email sent"} + return Message(message="Password recovery email sent") -@router.post("/reset-password/", response_model=schemas.Msg) +@router.post("/reset-password/") def reset_password( - token: str = Body(...), - new_password: str = Body(...), - db: Session = Depends(deps.get_db), -) -> Any: + session: SessionDep, + body: NewPassword, +) -> Message: """ Reset password """ - email = verify_password_reset_token(token) + email = verify_password_reset_token(token=body.token) if not email: raise HTTPException(status_code=400, detail="Invalid token") - user = crud.user.get_by_email(db, email=email) + user = crud.get_user_by_email(session=session, email=email) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system.", ) - elif not crud.user.is_active(user): + elif not user.is_active: raise HTTPException(status_code=400, detail="Inactive user") - hashed_password = get_password_hash(new_password) + hashed_password = get_password_hash(password=body.new_password) user.hashed_password = hashed_password - db.add(user) - db.commit() - return {"msg": "Password updated successfully"} + session.add(user) + session.commit() + return Message(message="Password updated successfully") diff --git a/src/backend/app/app/api/deps.py b/src/backend/app/app/api/deps.py index 552f0a2818..abe067b55f 100644 --- a/src/backend/app/app/api/deps.py +++ b/src/backend/app/app/api/deps.py @@ -39,18 +39,12 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User: user = session.get(User, token_data.sub) if not user: raise HTTPException(status_code=404, detail="User not found") - return user - - -def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)] -) -> User: - if not current_user.is_active: + if not user.is_active: raise HTTPException(status_code=400, detail="Inactive user") - return current_user + return user -CurrentUser = Annotated[User, Depends(get_current_active_user)] +CurrentUser = Annotated[User, Depends(get_current_user)] def get_current_active_superuser(current_user: CurrentUser) -> User: diff --git a/src/backend/app/app/crud/__init__.py b/src/backend/app/app/crud/__init__.py index b2b325069c..d452138321 100644 --- a/src/backend/app/app/crud/__init__.py +++ b/src/backend/app/app/crud/__init__.py @@ -9,7 +9,7 @@ # item = CRUDBase[Item, ItemCreate, ItemUpdate](Item) from sqlmodel import Session, select -from app.core.security import get_password_hash +from app.core.security import get_password_hash, verify_password from app.models import UserCreate, User @@ -27,3 +27,12 @@ def get_user_by_email(*, session: Session, email: str) -> User | None: statement = select(User).where(User.email == email) session_user = session.exec(statement).first() return session_user + + +def authenticate(*, session: Session, email: str, password: str) -> User | None: + user = get_user_by_email(session=session, email=email) + if not user: + return None + if not verify_password(password, user.hashed_password): + return None + return user diff --git a/src/backend/app/app/models.py b/src/backend/app/app/models.py index 590c3e92aa..ca860c2021 100644 --- a/src/backend/app/app/models.py +++ b/src/backend/app/app/models.py @@ -73,16 +73,20 @@ class ItemOut(ItemBase): # Generic message -class Msg(BaseModel): - msg: str +class Message(BaseModel): + message: str # JSON payload containing access token class Token(BaseModel): access_token: str - token_type: str + token_type: str = "bearer" # Contents of JWT token class TokenPayload(BaseModel): sub: Union[int, None] = None + +class NewPassword(BaseModel): + token: str + new_password: str From 0bfb7f89be275cf3ec355ea1b3c90dfdc33bfcfa Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Dec 2023 18:07:13 +0000 Subject: [PATCH 145/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2db4d2fd6f..e9b657dfc7 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Update code for login API. PR [#571](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/571) by [@tiangolo](https://github.com/tiangolo). * ✨ Add client in frontend and client generation. PR [#569](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/569) by [@alejsdev](https://github.com/alejsdev). * 🐳 Set up Docker config for new-frontend. PR [#564](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/564) by [@alejsdev](https://github.com/alejsdev). * ✨ Set up new frontend with Vite, TypeScript and React. PR [#563](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/563) by [@alejsdev](https://github.com/alejsdev). From 5465e62ed9de7fc6a43c94fafa60b88843ae8241 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:37:05 -0500 Subject: [PATCH 146/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20type=20ig?= =?UTF-8?q?nores=20and=20add=20`response=5Fmodel`=20(#572)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/app/api/api_v1/endpoints/items.py | 34 ++++++++--------- .../app/app/api/api_v1/endpoints/login.py | 5 +-- .../app/app/api/api_v1/endpoints/users.py | 38 +++++++++++-------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/backend/app/app/api/api_v1/endpoints/items.py b/src/backend/app/app/api/api_v1/endpoints/items.py index f2ff1de59d..99a83a2514 100644 --- a/src/backend/app/app/api/api_v1/endpoints/items.py +++ b/src/backend/app/app/api/api_v1/endpoints/items.py @@ -1,4 +1,4 @@ -from typing import Annotated +from typing import Annotated, Any from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select @@ -9,17 +9,17 @@ router = APIRouter() -@router.get("/") +@router.get("/", response_model=list[ItemOut]) def read_items( session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100 -) -> list[ItemOut]: +) -> Any: """ Retrieve items. """ if current_user.is_superuser: statement = select(Item).offset(skip).limit(limit) - return session.exec(statement).all() # type: ignore + return session.exec(statement).all() else: statement = ( select(Item) @@ -27,11 +27,11 @@ def read_items( .offset(skip) .limit(limit) ) - return session.exec(statement).all() # type: ignore + return session.exec(statement).all() -@router.get("/{id}") -def read_item(session: SessionDep, current_user: CurrentUser, id: int) -> ItemOut: +@router.get("/{id}", response_model=ItemOut) +def read_item(session: SessionDep, current_user: CurrentUser, id: int) -> Any: """ Get item by ID. """ @@ -40,13 +40,13 @@ def read_item(session: SessionDep, current_user: CurrentUser, id: int) -> ItemOu raise HTTPException(status_code=404, detail="Item not found") if not current_user.is_superuser and (item.owner_id != current_user.id): raise HTTPException(status_code=400, detail="Not enough permissions") - return item # type: ignore + return item -@router.post("/") +@router.post("/", response_model=ItemOut) def create_item( *, session: SessionDep, current_user: CurrentUser, item_in: ItemCreate -) -> ItemOut: +) -> Any: """ Create new item. """ @@ -54,13 +54,13 @@ def create_item( session.add(item) session.commit() session.refresh(item) - return item # type: ignore + return item -@router.put("/{id}") +@router.put("/{id}", response_model=ItemOut) def update_item( *, session: SessionDep, current_user: CurrentUser, id: int, item_in: ItemUpdate -) -> ItemOut: +) -> Any: """ Update an item. """ @@ -75,11 +75,11 @@ def update_item( session.add(item) session.commit() session.refresh(item) - return item # type: ignore + return item -@router.delete("/{id}") -def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> ItemOut: +@router.delete("/{id}", response_model=ItemOut) +def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> Any: """ Delete an item. """ @@ -90,4 +90,4 @@ def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> Item raise HTTPException(status_code=400, detail="Not enough permissions") session.delete(item) session.commit() - return item # type: ignore + return item diff --git a/src/backend/app/app/api/api_v1/endpoints/login.py b/src/backend/app/app/api/api_v1/endpoints/login.py index 15f0f3f095..4b741ce880 100644 --- a/src/backend/app/app/api/api_v1/endpoints/login.py +++ b/src/backend/app/app/api/api_v1/endpoints/login.py @@ -69,10 +69,7 @@ def recover_password(email: str, session: SessionDep) -> Message: @router.post("/reset-password/") -def reset_password( - session: SessionDep, - body: NewPassword, -) -> Message: +def reset_password(session: SessionDep, body: NewPassword) -> Message: """ Reset password """ diff --git a/src/backend/app/app/api/api_v1/endpoints/users.py b/src/backend/app/app/api/api_v1/endpoints/users.py index 755580eeef..0c244f8036 100644 --- a/src/backend/app/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/app/api/api_v1/endpoints/users.py @@ -18,18 +18,24 @@ router = APIRouter() -@router.get("/", dependencies=[Depends(get_current_active_superuser)]) -def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> List[UserOut]: +@router.get( + "/", + dependencies=[Depends(get_current_active_superuser)], + response_model=List[UserOut], +) +def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: """ Retrieve users. """ statement = select(User).offset(skip).limit(limit) users = session.exec(statement).all() - return users # type: ignore + return users -@router.post("/", dependencies=[Depends(get_current_active_superuser)]) -def create_user(*, session: SessionDep, user_in: UserCreate) -> UserOut: +@router.post( + "/", dependencies=[Depends(get_current_active_superuser)], response_model=UserOut +) +def create_user(*, session: SessionDep, user_in: UserCreate) -> Any: """ Create new user. """ @@ -45,7 +51,7 @@ def create_user(*, session: SessionDep, user_in: UserCreate) -> UserOut: send_new_account_email( email_to=user_in.email, username=user_in.email, password=user_in.password ) - return user # type: ignore + return user # TODO: Refactor when SQLModel has update @@ -73,16 +79,16 @@ def create_user(*, session: SessionDep, user_in: UserCreate) -> UserOut: # return user -@router.get("/me") -def read_user_me(session: SessionDep, current_user: CurrentUser) -> UserOut: +@router.get("/me", response_model=UserOut) +def read_user_me(session: SessionDep, current_user: CurrentUser) -> Any: """ Get current user. """ - return current_user # type: ignore + return current_user -@router.post("/open") -def create_user_open(session: SessionDep, user_in: UserCreateOpen) -> UserOut: +@router.post("/open", response_model=UserOut) +def create_user_open(session: SessionDep, user_in: UserCreateOpen) -> Any: """ Create new user without the need to be logged in. """ @@ -99,26 +105,26 @@ def create_user_open(session: SessionDep, user_in: UserCreateOpen) -> UserOut: ) user_create = UserCreate.from_orm(user_in) user = crud.create_user(session=session, user_create=user_create) - return user # type: ignore + return user -@router.get("/{user_id}") +@router.get("/{user_id}", response_model=UserOut) def read_user_by_id( user_id: int, session: SessionDep, current_user: CurrentUser -) -> UserOut: +) -> Any: """ Get a specific user by id. """ user = session.get(User, user_id) if user == current_user: - return user # type: ignore + return user if not current_user.is_superuser: raise HTTPException( # TODO: Review status code status_code=400, detail="The user doesn't have enough privileges", ) - return user # type: ignore + return user # TODO: Refactor when SQLModel has update From c962ef552a0c397b9a082840a06de4b27047b2e8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Dec 2023 18:37:23 +0000 Subject: [PATCH 147/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e9b657dfc7..38dd5ccf25 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* ♻️ Remove type ignores and add `response_model`. PR [#572](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/572) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor Users API and dependencies. PR [#561](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/561) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor frontend Docker build setup, use plain NodeJS, use custom Nginx config, fix build for old Vue. PR [#555](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/555) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor project generation, discard cookiecutter, use plain git/clone/fork. PR [#553](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/553) by [@tiangolo](https://github.com/tiangolo). From f0b7af829ee216aea616907acd235591ace6f42a Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:49:36 -0500 Subject: [PATCH 148/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20API=20i?= =?UTF-8?q?n=20`utils.py`=20(#573)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/app/api/api_v1/endpoints/utils.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/backend/app/app/api/api_v1/endpoints/utils.py b/src/backend/app/app/api/api_v1/endpoints/utils.py index 71fe68d0ce..ee8d105fdf 100644 --- a/src/backend/app/app/api/api_v1/endpoints/utils.py +++ b/src/backend/app/app/api/api_v1/endpoints/utils.py @@ -1,35 +1,35 @@ -from typing import Any - from fastapi import APIRouter, Depends from pydantic.networks import EmailStr -from app import models, schemas -from app.api import deps +from app.api.deps import get_current_active_superuser from app.core.celery_app import celery_app +from app.models import Message from app.utils import send_test_email router = APIRouter() -@router.post("/test-celery/", response_model=schemas.Msg, status_code=201) -def test_celery( - msg: schemas.Msg, - current_user: models.User = Depends(deps.get_current_active_superuser), -) -> Any: +@router.post( + "/test-celery/", + dependencies=[Depends(get_current_active_superuser)], + status_code=201, +) +def test_celery(body: Message) -> Message: """ Test Celery worker. """ - celery_app.send_task("app.worker.test_celery", args=[msg.msg]) - return {"msg": "Word received"} + celery_app.send_task("app.worker.test_celery", args=[body.message]) + return Message(message="Word received") -@router.post("/test-email/", response_model=schemas.Msg, status_code=201) -def test_email( - email_to: EmailStr, - current_user: models.User = Depends(deps.get_current_active_superuser), -) -> Any: +@router.post( + "/test-email/", + dependencies=[Depends(get_current_active_superuser)], + status_code=201, +) +def test_email(email_to: EmailStr) -> Message: """ Test emails. """ send_test_email(email_to=email_to) - return {"msg": "Test email sent"} + return Message(message="Test email sent") From 0c5674345ff539690ff9d25e3d2d351c4e22af9d Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Dec 2023 18:49:56 +0000 Subject: [PATCH 149/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 38dd5ccf25..25aed94236 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ♻️ Refactor API in `utils.py`. PR [#573](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/573) by [@alejsdev](https://github.com/alejsdev). * ✨ Update code for login API. PR [#571](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/571) by [@tiangolo](https://github.com/tiangolo). * ✨ Add client in frontend and client generation. PR [#569](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/569) by [@alejsdev](https://github.com/alejsdev). * 🐳 Set up Docker config for new-frontend. PR [#564](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/564) by [@alejsdev](https://github.com/alejsdev). From 10708fa108faedd471252ac1f8a0363df3eac817 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:39:42 -0500 Subject: [PATCH 150/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Re-enable=20user?= =?UTF-8?q?=20update=20path=20operations=20for=20frontend=20client=20gener?= =?UTF-8?q?ation=20(#574)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/app/api/api_v1/endpoints/items.py | 8 +- .../app/app/api/api_v1/endpoints/users.py | 99 ++++++++++--------- src/backend/app/app/models.py | 6 ++ 3 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/backend/app/app/api/api_v1/endpoints/items.py b/src/backend/app/app/api/api_v1/endpoints/items.py index 99a83a2514..c72a28fd7f 100644 --- a/src/backend/app/app/api/api_v1/endpoints/items.py +++ b/src/backend/app/app/api/api_v1/endpoints/items.py @@ -1,7 +1,7 @@ -from typing import Annotated, Any +from typing import Any -from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import Session, select +from fastapi import APIRouter, HTTPException +from sqlmodel import select from app.api.deps import CurrentUser, SessionDep from app.models import Item, ItemCreate, ItemOut, ItemUpdate @@ -90,4 +90,4 @@ def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> Any: raise HTTPException(status_code=400, detail="Not enough permissions") session.delete(item) session.commit() - return item + return item diff --git a/src/backend/app/app/api/api_v1/endpoints/users.py b/src/backend/app/app/api/api_v1/endpoints/users.py index 0c244f8036..f16a77d385 100644 --- a/src/backend/app/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/app/api/api_v1/endpoints/users.py @@ -1,8 +1,6 @@ -from typing import Annotated, Any, List +from typing import Any, List -from fastapi import APIRouter, Body, Depends, HTTPException -from fastapi.encoders import jsonable_encoder -from pydantic.networks import EmailStr +from fastapi import APIRouter, Depends, HTTPException from sqlmodel import select from app import crud @@ -12,7 +10,14 @@ get_current_active_superuser, ) from app.core.config import settings -from app.models import User, UserCreate, UserCreateOpen, UserOut, UserUpdate +from app.models import ( + User, + UserCreate, + UserCreateOpen, + UserOut, + UserUpdate, + UserUpdateMe, +) from app.utils import send_new_account_email router = APIRouter() @@ -54,29 +59,24 @@ def create_user(*, session: SessionDep, user_in: UserCreate) -> Any: return user -# TODO: Refactor when SQLModel has update -# @router.put("/me") -# def update_user_me( -# *, -# session: SessionDep, -# password: Annotated[str, Body(None)], -# full_name: Annotated[str, Body(None)], -# email: Annotated[EmailStr, Body(None)], -# current_user: CurrentUser, -# ) -> UserOut: -# """ -# Update own user. -# """ -# current_user_data = jsonable_encoder(current_user) -# user_in = UserUpdate(**current_user_data) -# if password is not None: -# user_in.password = password -# if full_name is not None: -# user_in.full_name = full_name -# if email is not None: -# user_in.email = email -# user = crud.user.update(session, session_obj=current_user, obj_in=user_in) -# return user +@router.put("/me", response_model=UserOut) +def update_user_me( + *, session: SessionDep, body: UserUpdateMe, current_user: CurrentUser +) -> Any: + """ + Update own user. + """ + # TODO: Refactor when SQLModel has update + # current_user_data = jsonable_encoder(current_user) + # user_in = UserUpdate(**current_user_data) + # if password is not None: + # user_in.password = password + # if full_name is not None: + # user_in.full_name = full_name + # if email is not None: + # user_in.email = email + # user = crud.user.update(session, session_obj=current_user, obj_in=user_in) + # return user @router.get("/me", response_model=UserOut) @@ -127,22 +127,27 @@ def read_user_by_id( return user -# TODO: Refactor when SQLModel has update -# @router.put("/{user_id}", dependencies=[Depends(get_current_active_superuser)]) -# def update_user( -# *, -# session: SessionDep, -# user_id: int, -# user_in: UserUpdate, -# ) -> UserOut: -# """ -# Update a user. -# """ -# user = session.get(User, user_id) -# if not user: -# raise HTTPException( -# status_code=404, -# detail="The user with this username does not exist in the system", -# ) -# user = crud.user.update(session, db_obj=user, obj_in=user_in) -# return user # type: ignore +@router.put( + "/{user_id}", + dependencies=[Depends(get_current_active_superuser)], + response_model=UserOut, +) +def update_user( + *, + session: SessionDep, + user_id: int, + user_in: UserUpdate, +) -> Any: + """ + Update a user. + """ + + # TODO: Refactor when SQLModel has update + # user = session.get(User, user_id) + # if not user: + # raise HTTPException( + # status_code=404, + # detail="The user with this username does not exist in the system", + # ) + # user = crud.user.update(session, db_obj=user, obj_in=user_in) + # return user diff --git a/src/backend/app/app/models.py b/src/backend/app/app/models.py index ca860c2021..bb03308a70 100644 --- a/src/backend/app/app/models.py +++ b/src/backend/app/app/models.py @@ -29,6 +29,11 @@ class UserUpdate(UserBase): password: Union[str, None] = None +class UserUpdateMe(BaseModel): + password: Union[str, None] = None + full_name: Union[str, None] = None + email: Union[EmailStr, None] = None + # Database model, database table inferred from class name class User(UserBase, table=True): id: Union[int, None] = Field(default=None, primary_key=True) @@ -87,6 +92,7 @@ class Token(BaseModel): class TokenPayload(BaseModel): sub: Union[int, None] = None + class NewPassword(BaseModel): token: str new_password: str From deb860d9ffcfad752ac78929ebc00e2e6b41e579 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Dec 2023 19:40:02 +0000 Subject: [PATCH 151/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 25aed94236..f5176f5935 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* ♻️ Re-enable user update path operations for frontend client generation. PR [#574](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/574) by [@alejsdev](https://github.com/alejsdev). * ♻️ Remove type ignores and add `response_model`. PR [#572](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/572) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor Users API and dependencies. PR [#561](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/561) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor frontend Docker build setup, use plain NodeJS, use custom Nginx config, fix build for old Vue. PR [#555](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/555) by [@tiangolo](https://github.com/tiangolo). From 3b22cd552fff574b2da35fcd5f6eab8888e9fc93 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:50:48 -0500 Subject: [PATCH 152/771] =?UTF-8?q?=E2=9C=A8=20Regenerate=20frontend=20cli?= =?UTF-8?q?ent=20with=20recent=20changes=20(#575)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/src/client/index.ts | 7 +-- .../src/client/models/{Msg.ts => Message.ts} | 4 +- ...login_reset_password.ts => NewPassword.ts} | 2 +- src/new-frontend/src/client/models/Token.ts | 2 +- .../client/models/{User.ts => UserUpdate.ts} | 4 +- .../src/client/models/UserUpdateMe.ts | 10 ++++ .../src/client/services/LoginService.ts | 20 ++++---- .../src/client/services/UsersService.ts | 51 +++++++++++++++++++ .../src/client/services/UtilsService.ts | 12 ++--- 9 files changed, 87 insertions(+), 25 deletions(-) rename src/new-frontend/src/client/models/{Msg.ts => Message.ts} (75%) rename src/new-frontend/src/client/models/{Body_login_reset_password.ts => NewPassword.ts} (81%) rename src/new-frontend/src/client/models/{User.ts => UserUpdate.ts} (82%) create mode 100644 src/new-frontend/src/client/models/UserUpdateMe.ts diff --git a/src/new-frontend/src/client/index.ts b/src/new-frontend/src/client/index.ts index 98461a0bef..baff3c22a0 100644 --- a/src/new-frontend/src/client/index.ts +++ b/src/new-frontend/src/client/index.ts @@ -8,17 +8,18 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; export type { Body_login_login_access_token } from './models/Body_login_login_access_token'; -export type { Body_login_reset_password } from './models/Body_login_reset_password'; export type { HTTPValidationError } from './models/HTTPValidationError'; export type { ItemCreate } from './models/ItemCreate'; export type { ItemOut } from './models/ItemOut'; export type { ItemUpdate } from './models/ItemUpdate'; -export type { Msg } from './models/Msg'; +export type { Message } from './models/Message'; +export type { NewPassword } from './models/NewPassword'; export type { Token } from './models/Token'; -export type { User } from './models/User'; export type { UserCreate } from './models/UserCreate'; export type { UserCreateOpen } from './models/UserCreateOpen'; export type { UserOut } from './models/UserOut'; +export type { UserUpdate } from './models/UserUpdate'; +export type { UserUpdateMe } from './models/UserUpdateMe'; export type { ValidationError } from './models/ValidationError'; export { ItemsService } from './services/ItemsService'; diff --git a/src/new-frontend/src/client/models/Msg.ts b/src/new-frontend/src/client/models/Message.ts similarity index 75% rename from src/new-frontend/src/client/models/Msg.ts rename to src/new-frontend/src/client/models/Message.ts index f087de476a..6220861f34 100644 --- a/src/new-frontend/src/client/models/Msg.ts +++ b/src/new-frontend/src/client/models/Message.ts @@ -3,6 +3,6 @@ /* tslint:disable */ /* eslint-disable */ -export type Msg = { - msg: string; +export type Message = { + message: string; }; diff --git a/src/new-frontend/src/client/models/Body_login_reset_password.ts b/src/new-frontend/src/client/models/NewPassword.ts similarity index 81% rename from src/new-frontend/src/client/models/Body_login_reset_password.ts rename to src/new-frontend/src/client/models/NewPassword.ts index 0ca32994b5..9d5512b3bf 100644 --- a/src/new-frontend/src/client/models/Body_login_reset_password.ts +++ b/src/new-frontend/src/client/models/NewPassword.ts @@ -3,7 +3,7 @@ /* tslint:disable */ /* eslint-disable */ -export type Body_login_reset_password = { +export type NewPassword = { token: string; new_password: string; }; diff --git a/src/new-frontend/src/client/models/Token.ts b/src/new-frontend/src/client/models/Token.ts index c58838f9a1..ea3120bf11 100644 --- a/src/new-frontend/src/client/models/Token.ts +++ b/src/new-frontend/src/client/models/Token.ts @@ -5,5 +5,5 @@ export type Token = { access_token: string; - token_type: string; + token_type?: string; }; diff --git a/src/new-frontend/src/client/models/User.ts b/src/new-frontend/src/client/models/UserUpdate.ts similarity index 82% rename from src/new-frontend/src/client/models/User.ts rename to src/new-frontend/src/client/models/UserUpdate.ts index ff390acb93..d64e3da644 100644 --- a/src/new-frontend/src/client/models/User.ts +++ b/src/new-frontend/src/client/models/UserUpdate.ts @@ -3,10 +3,10 @@ /* tslint:disable */ /* eslint-disable */ -export type User = { +export type UserUpdate = { email?: string; is_active?: boolean; is_superuser?: boolean; full_name?: string; - id?: number; + password?: string; }; diff --git a/src/new-frontend/src/client/models/UserUpdateMe.ts b/src/new-frontend/src/client/models/UserUpdateMe.ts new file mode 100644 index 0000000000..bfed1a4f23 --- /dev/null +++ b/src/new-frontend/src/client/models/UserUpdateMe.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type UserUpdateMe = { + password?: string; + full_name?: string; + email?: string; +}; diff --git a/src/new-frontend/src/client/services/LoginService.ts b/src/new-frontend/src/client/services/LoginService.ts index a5fd1270a0..e40899d38c 100644 --- a/src/new-frontend/src/client/services/LoginService.ts +++ b/src/new-frontend/src/client/services/LoginService.ts @@ -3,10 +3,10 @@ /* tslint:disable */ /* eslint-disable */ import type { Body_login_login_access_token } from '../models/Body_login_login_access_token'; -import type { Body_login_reset_password } from '../models/Body_login_reset_password'; -import type { Msg } from '../models/Msg'; +import type { Message } from '../models/Message'; +import type { NewPassword } from '../models/NewPassword'; import type { Token } from '../models/Token'; -import type { User } from '../models/User'; +import type { UserOut } from '../models/UserOut'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; @@ -39,10 +39,10 @@ formData: Body_login_login_access_token, /** * Test Token * Test access token - * @returns User Successful Response + * @returns UserOut Successful Response * @throws ApiError */ - public static testToken(): CancelablePromise { + public static testToken(): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/login/test-token', @@ -52,14 +52,14 @@ formData: Body_login_login_access_token, /** * Recover Password * Password Recovery - * @returns Msg Successful Response + * @returns Message Successful Response * @throws ApiError */ public static recoverPassword({ email, }: { email: string, -}): CancelablePromise { +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/password-recovery/{email}', @@ -75,14 +75,14 @@ email: string, /** * Reset Password * Reset password - * @returns Msg Successful Response + * @returns Message Successful Response * @throws ApiError */ public static resetPassword({ requestBody, }: { -requestBody: Body_login_reset_password, -}): CancelablePromise { +requestBody: NewPassword, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/reset-password/', diff --git a/src/new-frontend/src/client/services/UsersService.ts b/src/new-frontend/src/client/services/UsersService.ts index b6cfa1fe2a..759f94279f 100644 --- a/src/new-frontend/src/client/services/UsersService.ts +++ b/src/new-frontend/src/client/services/UsersService.ts @@ -5,6 +5,8 @@ import type { UserCreate } from '../models/UserCreate'; import type { UserCreateOpen } from '../models/UserCreateOpen'; import type { UserOut } from '../models/UserOut'; +import type { UserUpdate } from '../models/UserUpdate'; +import type { UserUpdateMe } from '../models/UserUpdateMe'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; @@ -73,6 +75,28 @@ requestBody: UserCreate, }); } + /** + * Update User Me + * Update own user. + * @returns UserOut Successful Response + * @throws ApiError + */ + public static updateUserMe({ +requestBody, +}: { +requestBody: UserUpdateMe, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/v1/users/me', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** * Create User Open * Create new user without the need to be logged in. @@ -118,4 +142,31 @@ userId: number, }); } + /** + * Update User + * Update a user. + * @returns UserOut Successful Response + * @throws ApiError + */ + public static updateUser({ +userId, +requestBody, +}: { +userId: number, +requestBody: UserUpdate, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/v1/users/{user_id}', + path: { + 'user_id': userId, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + } diff --git a/src/new-frontend/src/client/services/UtilsService.ts b/src/new-frontend/src/client/services/UtilsService.ts index 7b34b71112..a7ed0bb4f7 100644 --- a/src/new-frontend/src/client/services/UtilsService.ts +++ b/src/new-frontend/src/client/services/UtilsService.ts @@ -2,7 +2,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Msg } from '../models/Msg'; +import type { Message } from '../models/Message'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; @@ -13,14 +13,14 @@ export class UtilsService { /** * Test Celery * Test Celery worker. - * @returns Msg Successful Response + * @returns Message Successful Response * @throws ApiError */ public static testCelery({ requestBody, }: { -requestBody: Msg, -}): CancelablePromise { +requestBody: Message, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/utils/test-celery/', @@ -35,14 +35,14 @@ requestBody: Msg, /** * Test Email * Test emails. - * @returns Msg Successful Response + * @returns Message Successful Response * @throws ApiError */ public static testEmail({ emailTo, }: { emailTo: string, -}): CancelablePromise { +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/utils/test-email/', From 9b2d40b99c15b2102f256e84a3a36200221f5af6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Dec 2023 19:51:08 +0000 Subject: [PATCH 153/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f5176f5935..b7f0a0f421 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Regenerate frontend client with recent changes. PR [#575](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/575) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor API in `utils.py`. PR [#573](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/573) by [@alejsdev](https://github.com/alejsdev). * ✨ Update code for login API. PR [#571](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/571) by [@tiangolo](https://github.com/tiangolo). * ✨ Add client in frontend and client generation. PR [#569](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/569) by [@alejsdev](https://github.com/alejsdev). From 705b6efb3f7e250712fa09a563fdc72ffb6ccda1 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:16:21 -0500 Subject: [PATCH 154/771] =?UTF-8?q?=E2=9C=A8=20Include=20schemas=20in=20ge?= =?UTF-8?q?nerated=20frontend=20client=20(#584)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/package.json | 2 +- src/new-frontend/src/client/index.ts | 15 ++++++++++ .../schemas/$Body_login_login_access_token.ts | 29 +++++++++++++++++++ .../client/schemas/$HTTPValidationError.ts | 14 +++++++++ .../src/client/schemas/$ItemCreate.ts | 15 ++++++++++ .../src/client/schemas/$ItemOut.ts | 19 ++++++++++++ .../src/client/schemas/$ItemUpdate.ts | 14 +++++++++ .../src/client/schemas/$Message.ts | 12 ++++++++ .../src/client/schemas/$NewPassword.ts | 16 ++++++++++ src/new-frontend/src/client/schemas/$Token.ts | 15 ++++++++++ .../src/client/schemas/$UserCreate.ts | 26 +++++++++++++++++ .../src/client/schemas/$UserCreateOpen.ts | 20 +++++++++++++ .../src/client/schemas/$UserOut.ts | 26 +++++++++++++++++ .../src/client/schemas/$UserUpdate.ts | 24 +++++++++++++++ .../src/client/schemas/$UserUpdateMe.ts | 18 ++++++++++++ .../src/client/schemas/$ValidationError.ts | 28 ++++++++++++++++++ 16 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts create mode 100644 src/new-frontend/src/client/schemas/$HTTPValidationError.ts create mode 100644 src/new-frontend/src/client/schemas/$ItemCreate.ts create mode 100644 src/new-frontend/src/client/schemas/$ItemOut.ts create mode 100644 src/new-frontend/src/client/schemas/$ItemUpdate.ts create mode 100644 src/new-frontend/src/client/schemas/$Message.ts create mode 100644 src/new-frontend/src/client/schemas/$NewPassword.ts create mode 100644 src/new-frontend/src/client/schemas/$Token.ts create mode 100644 src/new-frontend/src/client/schemas/$UserCreate.ts create mode 100644 src/new-frontend/src/client/schemas/$UserCreateOpen.ts create mode 100644 src/new-frontend/src/client/schemas/$UserOut.ts create mode 100644 src/new-frontend/src/client/schemas/$UserUpdate.ts create mode 100644 src/new-frontend/src/client/schemas/$UserUpdateMe.ts create mode 100644 src/new-frontend/src/client/schemas/$ValidationError.ts diff --git a/src/new-frontend/package.json b/src/new-frontend/package.json index c7cfd8b42a..bc9ce06b2e 100644 --- a/src/new-frontend/package.json +++ b/src/new-frontend/package.json @@ -8,7 +8,7 @@ "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", - "generate-client": "openapi --input ./openapi.json --useOptions --useUnionTypes --output ./src/client --client axios" + "generate-client": "openapi --input ./openapi.json --useOptions --useUnionTypes --output ./src/client --client axios --exportSchemas true" }, "dependencies": { "axios": "1.6.2", diff --git a/src/new-frontend/src/client/index.ts b/src/new-frontend/src/client/index.ts index baff3c22a0..59b24038bb 100644 --- a/src/new-frontend/src/client/index.ts +++ b/src/new-frontend/src/client/index.ts @@ -22,6 +22,21 @@ export type { UserUpdate } from './models/UserUpdate'; export type { UserUpdateMe } from './models/UserUpdateMe'; export type { ValidationError } from './models/ValidationError'; +export { $Body_login_login_access_token } from './schemas/$Body_login_login_access_token'; +export { $HTTPValidationError } from './schemas/$HTTPValidationError'; +export { $ItemCreate } from './schemas/$ItemCreate'; +export { $ItemOut } from './schemas/$ItemOut'; +export { $ItemUpdate } from './schemas/$ItemUpdate'; +export { $Message } from './schemas/$Message'; +export { $NewPassword } from './schemas/$NewPassword'; +export { $Token } from './schemas/$Token'; +export { $UserCreate } from './schemas/$UserCreate'; +export { $UserCreateOpen } from './schemas/$UserCreateOpen'; +export { $UserOut } from './schemas/$UserOut'; +export { $UserUpdate } from './schemas/$UserUpdate'; +export { $UserUpdateMe } from './schemas/$UserUpdateMe'; +export { $ValidationError } from './schemas/$ValidationError'; + export { ItemsService } from './services/ItemsService'; export { LoginService } from './services/LoginService'; export { UsersService } from './services/UsersService'; diff --git a/src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts b/src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts new file mode 100644 index 0000000000..81547d339b --- /dev/null +++ b/src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts @@ -0,0 +1,29 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $Body_login_login_access_token = { + properties: { + grant_type: { + type: 'string', + pattern: 'password', +}, + username: { + type: 'string', + isRequired: true, +}, + password: { + type: 'string', + isRequired: true, +}, + scope: { + type: 'string', +}, + client_id: { + type: 'string', +}, + client_secret: { + type: 'string', +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$HTTPValidationError.ts b/src/new-frontend/src/client/schemas/$HTTPValidationError.ts new file mode 100644 index 0000000000..4206c7c49a --- /dev/null +++ b/src/new-frontend/src/client/schemas/$HTTPValidationError.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $HTTPValidationError = { + properties: { + detail: { + type: 'array', + contains: { + type: 'ValidationError', + }, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$ItemCreate.ts b/src/new-frontend/src/client/schemas/$ItemCreate.ts new file mode 100644 index 0000000000..11851241f8 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$ItemCreate.ts @@ -0,0 +1,15 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ItemCreate = { + properties: { + title: { + type: 'string', + isRequired: true, +}, + description: { + type: 'string', +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$ItemOut.ts b/src/new-frontend/src/client/schemas/$ItemOut.ts new file mode 100644 index 0000000000..6eac037db4 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$ItemOut.ts @@ -0,0 +1,19 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ItemOut = { + properties: { + title: { + type: 'string', + isRequired: true, +}, + description: { + type: 'string', +}, + id: { + type: 'number', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$ItemUpdate.ts b/src/new-frontend/src/client/schemas/$ItemUpdate.ts new file mode 100644 index 0000000000..25250d7e13 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$ItemUpdate.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ItemUpdate = { + properties: { + title: { + type: 'string', +}, + description: { + type: 'string', +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$Message.ts b/src/new-frontend/src/client/schemas/$Message.ts new file mode 100644 index 0000000000..d2abdc1351 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$Message.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $Message = { + properties: { + message: { + type: 'string', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$NewPassword.ts b/src/new-frontend/src/client/schemas/$NewPassword.ts new file mode 100644 index 0000000000..f6b19e2d36 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$NewPassword.ts @@ -0,0 +1,16 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $NewPassword = { + properties: { + token: { + type: 'string', + isRequired: true, +}, + new_password: { + type: 'string', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$Token.ts b/src/new-frontend/src/client/schemas/$Token.ts new file mode 100644 index 0000000000..88946bc623 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$Token.ts @@ -0,0 +1,15 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $Token = { + properties: { + access_token: { + type: 'string', + isRequired: true, +}, + token_type: { + type: 'string', +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$UserCreate.ts b/src/new-frontend/src/client/schemas/$UserCreate.ts new file mode 100644 index 0000000000..2275432a9e --- /dev/null +++ b/src/new-frontend/src/client/schemas/$UserCreate.ts @@ -0,0 +1,26 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $UserCreate = { + properties: { + email: { + type: 'string', + isRequired: true, + format: 'email', +}, + is_active: { + type: 'boolean', +}, + is_superuser: { + type: 'boolean', +}, + full_name: { + type: 'string', +}, + password: { + type: 'string', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$UserCreateOpen.ts b/src/new-frontend/src/client/schemas/$UserCreateOpen.ts new file mode 100644 index 0000000000..7aa75cd808 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$UserCreateOpen.ts @@ -0,0 +1,20 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $UserCreateOpen = { + properties: { + email: { + type: 'string', + isRequired: true, + format: 'email', +}, + password: { + type: 'string', + isRequired: true, +}, + full_name: { + type: 'string', +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$UserOut.ts b/src/new-frontend/src/client/schemas/$UserOut.ts new file mode 100644 index 0000000000..c920f098d1 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$UserOut.ts @@ -0,0 +1,26 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $UserOut = { + properties: { + email: { + type: 'string', + isRequired: true, + format: 'email', +}, + is_active: { + type: 'boolean', +}, + is_superuser: { + type: 'boolean', +}, + full_name: { + type: 'string', +}, + id: { + type: 'number', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$UserUpdate.ts b/src/new-frontend/src/client/schemas/$UserUpdate.ts new file mode 100644 index 0000000000..f0f38ac3c1 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$UserUpdate.ts @@ -0,0 +1,24 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $UserUpdate = { + properties: { + email: { + type: 'string', + format: 'email', +}, + is_active: { + type: 'boolean', +}, + is_superuser: { + type: 'boolean', +}, + full_name: { + type: 'string', +}, + password: { + type: 'string', +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$UserUpdateMe.ts b/src/new-frontend/src/client/schemas/$UserUpdateMe.ts new file mode 100644 index 0000000000..d1408a1893 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$UserUpdateMe.ts @@ -0,0 +1,18 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $UserUpdateMe = { + properties: { + password: { + type: 'string', +}, + full_name: { + type: 'string', +}, + email: { + type: 'string', + format: 'email', +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$ValidationError.ts b/src/new-frontend/src/client/schemas/$ValidationError.ts new file mode 100644 index 0000000000..b040db7c5c --- /dev/null +++ b/src/new-frontend/src/client/schemas/$ValidationError.ts @@ -0,0 +1,28 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ValidationError = { + properties: { + loc: { + type: 'array', + contains: { + type: 'any-of', + contains: [{ + type: 'string', +}, { + type: 'number', +}], +}, + isRequired: true, +}, + msg: { + type: 'string', + isRequired: true, +}, + type: { + type: 'string', + isRequired: true, +}, + }, +} as const; From 4a4efecf91e47dce883aadac55217c67571caa55 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jan 2024 20:21:50 +0000 Subject: [PATCH 155/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b7f0a0f421..e1ce03260c 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Include schemas in generated frontend client. PR [#584](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/584) by [@alejsdev](https://github.com/alejsdev). * ✨ Regenerate frontend client with recent changes. PR [#575](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/575) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor API in `utils.py`. PR [#573](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/573) by [@alejsdev](https://github.com/alejsdev). * ✨ Update code for login API. PR [#571](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/571) by [@tiangolo](https://github.com/tiangolo). From 167f059f4e75feec7761ebadbd4145cee71fef39 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:48:56 -0500 Subject: [PATCH 156/771] =?UTF-8?q?=E2=9C=A8=20Add=20Login=20to=20new=20fr?= =?UTF-8?q?ontend=20(#585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/index.html | 7 +- src/new-frontend/package.json | 10 ++- src/new-frontend/src/App.tsx | 15 ++-- .../src/assets/images/fastapi-logo.png | Bin 0 -> 17680 bytes .../src/assets/images/favicon.png | Bin 0 -> 412 bytes src/new-frontend/src/main.tsx | 21 +++-- src/new-frontend/src/pages/auth/Login.tsx | 72 ++++++++++++++++++ 7 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 src/new-frontend/src/assets/images/fastapi-logo.png create mode 100644 src/new-frontend/src/assets/images/favicon.png create mode 100644 src/new-frontend/src/pages/auth/Login.tsx diff --git a/src/new-frontend/index.html b/src/new-frontend/index.html index e4b78eae12..39bc239ba5 100644 --- a/src/new-frontend/index.html +++ b/src/new-frontend/index.html @@ -1,13 +1,14 @@ - + - Vite + React + TS + Full Stack Project Generator +
- + diff --git a/src/new-frontend/package.json b/src/new-frontend/package.json index bc9ce06b2e..e1187f3120 100644 --- a/src/new-frontend/package.json +++ b/src/new-frontend/package.json @@ -11,10 +11,18 @@ "generate-client": "openapi --input ./openapi.json --useOptions --useUnionTypes --output ./src/client --client axios --exportSchemas true" }, "dependencies": { + "@chakra-ui/icons": "2.1.1", + "@chakra-ui/react": "2.8.2", + "@emotion/react": "11.11.3", + "@emotion/styled": "11.11.0", + "@types/react-router-dom": "5.3.3", "axios": "1.6.2", "form-data": "4.0.0", + "framer-motion": "10.16.16", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-hook-form": "7.49.3", + "react-router-dom": "6.21.1" }, "devDependencies": { "@types/node": "20.10.5", diff --git a/src/new-frontend/src/App.tsx b/src/new-frontend/src/App.tsx index 6893d8cc2e..db440f54ad 100644 --- a/src/new-frontend/src/App.tsx +++ b/src/new-frontend/src/App.tsx @@ -1,11 +1,16 @@ -import './App.css' - -function App() { +import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import Login from './pages/auth/Login'; +import RecoverPassword from './pages/auth/RecoverPassword'; +function App() { return ( - <> - + + + } /> + } /> + + ) } diff --git a/src/new-frontend/src/assets/images/fastapi-logo.png b/src/new-frontend/src/assets/images/fastapi-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..57d9eec137c0458dd83539835397fdd299e650ed GIT binary patch literal 17680 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 XuqwmUh9A!VCZQy!E?XsS^7a1$wHKYW literal 0 HcmV?d00001 diff --git a/src/new-frontend/src/assets/images/favicon.png b/src/new-frontend/src/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..b3dcdd3090e52a3d94affb00a40df2fe8a56d4ba GIT binary patch literal 412 zcmV;N0b~A&P)PX* z{K4Sv{rBz-z`hUQ_xYc@=P{f3wCEBt;){?j=lD#CN0Dbl@rgY=9LX_2EDp%wKp9cI zMIx3-p{Zs%RFRksEYuLyRV5_kK_@^|uR#rs@lnXoM^snz5JHbR4E7P#D@Y6p1q@!) z!9SL*p^zBsi0UO+L&BQN_xdH98agIITODdKfgP$7A7V2%MCd7*AC`zS&wFg@kqB=} z=nRtsV`@l*p)v5uqQgGIYueH3m>eRA{b(IyphURB#|jeINrI@hBxe*(E%tWQ!K}gpBwmq{{`{%p(7v$68vzm0<1w0000 { + return localStorage.getItem('access_token') || ''; +} ReactDOM.createRoot(document.getElementById('root')!).render( - + + + , ) + diff --git a/src/new-frontend/src/pages/auth/Login.tsx b/src/new-frontend/src/pages/auth/Login.tsx new file mode 100644 index 0000000000..112ae1cea8 --- /dev/null +++ b/src/new-frontend/src/pages/auth/Login.tsx @@ -0,0 +1,72 @@ +import React from "react"; + +import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; +import { Button, Center, Container, FormControl, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from "@chakra-ui/react"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { Link as ReactRouterLink, useNavigate } from "react-router-dom"; + +import Logo from "../../assets/images/fastapi-logo.png"; +import { LoginService } from "../../client"; +import { Body_login_login_access_token as AccessToken } from "../../client/models/Body_login_login_access_token"; + +const Login: React.FC = () => { + const [show, setShow] = useBoolean(); + const navigate = useNavigate(); + const { register, handleSubmit } = useForm(); + + const onSubmit: SubmitHandler = async (data) => { + const response = await LoginService.loginAccessToken({ + formData: data, + }); + localStorage.setItem("access_token", response.access_token); + navigate("/"); + }; + + return ( + + FastAPI logo + + + + + + + + + {show ? : } + + + +
+ + Forgot password? + +
+
+ +
+ ); +}; + +export default Login; From 5836c766082962390d568e386bf1c51bead0d1f9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 23 Jan 2024 16:49:17 +0000 Subject: [PATCH 157/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e1ce03260c..c37866d988 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add Login to new frontend. PR [#585](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/585) by [@alejsdev](https://github.com/alejsdev). * ✨ Include schemas in generated frontend client. PR [#584](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/584) by [@alejsdev](https://github.com/alejsdev). * ✨ Regenerate frontend client with recent changes. PR [#575](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/575) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor API in `utils.py`. PR [#573](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/573) by [@alejsdev](https://github.com/alejsdev). From 1a27bdfa8cd6397d000b5af3bef2ad1699d64b0f Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:40:11 -0500 Subject: [PATCH 158/771] =?UTF-8?q?=E2=9C=A8=20Add=20Sidebar=20to=20new=20?= =?UTF-8?q?frontend=20(#587)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/src/components/Sidebar.tsx | 48 +++++++++++++++++++ .../src/components/SidebarItems.tsx | 37 ++++++++++++++ src/new-frontend/src/components/UserInfo.tsx | 40 ++++++++++++++++ src/new-frontend/src/pages/Layout.tsx | 14 ++++++ 4 files changed, 139 insertions(+) create mode 100644 src/new-frontend/src/components/Sidebar.tsx create mode 100644 src/new-frontend/src/components/SidebarItems.tsx create mode 100644 src/new-frontend/src/components/UserInfo.tsx create mode 100644 src/new-frontend/src/pages/Layout.tsx diff --git a/src/new-frontend/src/components/Sidebar.tsx b/src/new-frontend/src/components/Sidebar.tsx new file mode 100644 index 0000000000..d2f2e83334 --- /dev/null +++ b/src/new-frontend/src/components/Sidebar.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, useDisclosure } from '@chakra-ui/react'; +import { FiMenu } from 'react-icons/fi'; + +import Logo from "../assets/images/fastapi-logo.png"; +import SidebarItems from './SidebarItems'; +import UserInfo from './UserInfo'; + + +const Sidebar: React.FC = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + + return ( + <> + {/* Mobile */} + } /> + + + + + + + + Logo + + + + + + + + + {/* Desktop */} + + + + Logo + + + + + + + ); +} + +export default Sidebar; diff --git a/src/new-frontend/src/components/SidebarItems.tsx b/src/new-frontend/src/components/SidebarItems.tsx new file mode 100644 index 0000000000..8970410d8c --- /dev/null +++ b/src/new-frontend/src/components/SidebarItems.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +import { Flex, Icon, Text } from '@chakra-ui/react'; +import { FiBriefcase, FiHome, FiLogOut, FiUser, FiUsers } from 'react-icons/fi'; +import { Link } from 'react-router-dom'; + +const items = [ + { icon: FiHome, title: 'Dashboard', path: "/" }, + { icon: FiUser, title: 'Profile', path: "/profile" }, + { icon: FiBriefcase, title: 'Items', path: "/items" }, + { icon: FiUsers, title: 'Admin', path: "/admin" }, + { icon: FiLogOut, title: 'Log out' } +]; + +const SidebarItems: React.FC = () => { + const listItems = items.map((item) => ( + + + + + {item.title} + + + + )); + + return ( + <> + {listItems} + + ); +}; + +export default SidebarItems; diff --git a/src/new-frontend/src/components/UserInfo.tsx b/src/new-frontend/src/components/UserInfo.tsx new file mode 100644 index 0000000000..3cf059b3f2 --- /dev/null +++ b/src/new-frontend/src/components/UserInfo.tsx @@ -0,0 +1,40 @@ +import React, { useEffect, useState } from 'react'; + +import { Avatar, Flex, Skeleton, Text } from '@chakra-ui/react'; +import { FaUserAstronaut } from 'react-icons/fa'; + +import { UserOut, UsersService } from '../client'; + +const UserInfo: React.FC = () => { + const [userData, setUserData] = useState(); + + useEffect(() => { + const fetchUserData = async () => { + try { + const userResponse = await UsersService.readUserMe(); + setUserData(userResponse); + } catch (error) { + // TODO: Handle error to give feedback to the user + console.error(error); + } + }; + fetchUserData(); + }, []); + + return ( + <> + {userData ? ( + + } size='sm' alignSelf="center" /> + {/* TODO: Conditional tooltip based on email length */} + {userData.email} + + ) : + + } + + ); + +} + +export default UserInfo; \ No newline at end of file diff --git a/src/new-frontend/src/pages/Layout.tsx b/src/new-frontend/src/pages/Layout.tsx new file mode 100644 index 0000000000..80e78f16c9 --- /dev/null +++ b/src/new-frontend/src/pages/Layout.tsx @@ -0,0 +1,14 @@ +import Sidebar from '../components/Sidebar'; + +import { Flex } from '@chakra-ui/react'; + +const Layout = ({ children }: { children: React.ReactNode }) => { + return ( + + + {children} + + ); +}; + +export default Layout; From 46936dd3d03e72330fa1a4678315cb205c4e7675 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 29 Jan 2024 21:40:31 +0000 Subject: [PATCH 159/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c37866d988..c931c7d8c7 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add Sidebar to new frontend. PR [#587](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/587) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Login to new frontend. PR [#585](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/585) by [@alejsdev](https://github.com/alejsdev). * ✨ Include schemas in generated frontend client. PR [#584](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/584) by [@alejsdev](https://github.com/alejsdev). * ✨ Regenerate frontend client with recent changes. PR [#575](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/575) by [@alejsdev](https://github.com/alejsdev). From fafcba0fe23ac83b42122e40564618e6f7c0932e Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:10:46 -0500 Subject: [PATCH 160/771] =?UTF-8?q?=E2=9C=A8=20Add=20Layout=20to=20App=20(?= =?UTF-8?q?#588)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/src/App.tsx | 3 +++ src/new-frontend/src/pages/Layout.tsx | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/new-frontend/src/App.tsx b/src/new-frontend/src/App.tsx index db440f54ad..958baf13ca 100644 --- a/src/new-frontend/src/App.tsx +++ b/src/new-frontend/src/App.tsx @@ -1,5 +1,6 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import Layout from './pages/Layout'; import Login from './pages/auth/Login'; import RecoverPassword from './pages/auth/RecoverPassword'; @@ -9,6 +10,8 @@ function App() { } /> } /> + }> + ) diff --git a/src/new-frontend/src/pages/Layout.tsx b/src/new-frontend/src/pages/Layout.tsx index 80e78f16c9..bbf1087ce6 100644 --- a/src/new-frontend/src/pages/Layout.tsx +++ b/src/new-frontend/src/pages/Layout.tsx @@ -1,12 +1,13 @@ +import { Outlet } from 'react-router-dom'; import Sidebar from '../components/Sidebar'; import { Flex } from '@chakra-ui/react'; -const Layout = ({ children }: { children: React.ReactNode }) => { +const Layout = () => { return ( - {children} + ); }; From bfbb180cb45a969ccd9f38eccbcc7a7ae0c98d64 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 29 Jan 2024 22:11:07 +0000 Subject: [PATCH 161/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c931c7d8c7..1a39908b2b 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* ✨ Add Layout to App. PR [#588](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/588) by [@alejsdev](https://github.com/alejsdev). * ♻️ Re-enable user update path operations for frontend client generation. PR [#574](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/574) by [@alejsdev](https://github.com/alejsdev). * ♻️ Remove type ignores and add `response_model`. PR [#572](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/572) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor Users API and dependencies. PR [#561](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/561) by [@alejsdev](https://github.com/alejsdev). From 39f49d59bcf11933d70bba1b228fda8c9a27c985 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:42:09 -0500 Subject: [PATCH 162/771] =?UTF-8?q?=F0=9F=90=9B=20=20Add=20`onClose`=20to?= =?UTF-8?q?=20`SidebarItems`=20(#589)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/src/components/Sidebar.tsx | 2 +- src/new-frontend/src/components/SidebarItems.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/new-frontend/src/components/Sidebar.tsx b/src/new-frontend/src/components/Sidebar.tsx index d2f2e83334..a67623076f 100644 --- a/src/new-frontend/src/components/Sidebar.tsx +++ b/src/new-frontend/src/components/Sidebar.tsx @@ -23,7 +23,7 @@ const Sidebar: React.FC = () => { Logo - + diff --git a/src/new-frontend/src/components/SidebarItems.tsx b/src/new-frontend/src/components/SidebarItems.tsx index 8970410d8c..7790111f80 100644 --- a/src/new-frontend/src/components/SidebarItems.tsx +++ b/src/new-frontend/src/components/SidebarItems.tsx @@ -12,12 +12,16 @@ const items = [ { icon: FiLogOut, title: 'Log out' } ]; -const SidebarItems: React.FC = () => { +interface SidebarItemsProps { + onClose?: () => void; +} + +const SidebarItems: React.FC = ({ onClose }) => { const listItems = items.map((item) => ( + }} onClick={onClose}> From 642936c33d89dc1621d809ff53b4096fb13abc59 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 29 Jan 2024 22:42:30 +0000 Subject: [PATCH 163/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1a39908b2b..87d367d130 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ After using this generator, your new project (the directory created) will contai #### Fixes +* 🐛 Add `onClose` to `SidebarItems`. PR [#589](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/589) by [@alejsdev](https://github.com/alejsdev). * 🐛 Fix positional argument bug in `init_db.py`. PR [#562](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/562) by [@alejsdev](https://github.com/alejsdev). * 📌 Fix flower Docker image, pin version. PR [#396](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/396) by [@sanggusti](https://github.com/sanggusti). * 🐛 Fix Celery worker command. PR [#443](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/443) by [@bechtold](https://github.com/bechtold). From d4bdc3d9143f6aefa1cab6995ae25e578212d4ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:09:46 +0100 Subject: [PATCH 164/771] =?UTF-8?q?=E2=AC=86=20Bump=20tiangolo/issue-manag?= =?UTF-8?q?er=20from=200.2.0=20to=200.5.0=20(#591)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [tiangolo/issue-manager](https://github.com/tiangolo/issue-manager) from 0.2.0 to 0.5.0. - [Release notes](https://github.com/tiangolo/issue-manager/releases) - [Commits](https://github.com/tiangolo/issue-manager/compare/0.2.0...0.5.0) --- updated-dependencies: - dependency-name: tiangolo/issue-manager dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue-manager.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index 42748fbb2b..a96f24ab43 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -15,7 +15,7 @@ jobs: issue-manager: runs-on: ubuntu-latest steps: - - uses: tiangolo/issue-manager@0.2.0 + - uses: tiangolo/issue-manager@0.5.0 with: token: ${{ secrets.GITHUB_TOKEN }} config: > From a461893f021711098e9380f7d813f7012c88d6b2 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:03:08 -0500 Subject: [PATCH 165/771] =?UTF-8?q?=E2=9C=A8=20Add=20state=20store=20to=20?= =?UTF-8?q?new=20frontend=20(#592)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/package.json | 4 +++- src/new-frontend/src/store/items-store.tsx | 25 ++++++++++++++++++++++ src/new-frontend/src/store/user-store.tsx | 19 ++++++++++++++++ src/new-frontend/src/store/users-store.tsx | 21 ++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/new-frontend/src/store/items-store.tsx create mode 100644 src/new-frontend/src/store/user-store.tsx create mode 100644 src/new-frontend/src/store/users-store.tsx diff --git a/src/new-frontend/package.json b/src/new-frontend/package.json index e1187f3120..da94d40181 100644 --- a/src/new-frontend/package.json +++ b/src/new-frontend/package.json @@ -22,7 +22,9 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "7.49.3", - "react-router-dom": "6.21.1" + "react-icons": "5.0.1", + "react-router-dom": "6.21.1", + "zustand": "4.5.0" }, "devDependencies": { "@types/node": "20.10.5", diff --git a/src/new-frontend/src/store/items-store.tsx b/src/new-frontend/src/store/items-store.tsx new file mode 100644 index 0000000000..dbd6ac708f --- /dev/null +++ b/src/new-frontend/src/store/items-store.tsx @@ -0,0 +1,25 @@ +import { create } from "zustand"; +import { ItemCreate, ItemOut, ItemsService } from "../client"; + +interface ItemsStore { + items: ItemOut[]; + getItems: () => Promise; + addItem: (item: ItemCreate) => Promise; + deleteItem: (id: number) => Promise; +} + +export const useItemsStore = create((set) => ({ + items: [], + getItems: async () => { + const itemsResponse = await ItemsService.readItems({ skip: 0, limit: 10 }); + set({ items: itemsResponse }); + }, + addItem: async (item: ItemCreate) => { + const itemResponse = await ItemsService.createItem({ requestBody: item}); + set((state) => ({ items: [...state.items, itemResponse] })); + }, + deleteItem: async (id: number) => { + await ItemsService.deleteItem({ id }); + set((state) => ({ items: state.items.filter((item) => item.id !== id) })); + } +})); \ No newline at end of file diff --git a/src/new-frontend/src/store/user-store.tsx b/src/new-frontend/src/store/user-store.tsx new file mode 100644 index 0000000000..0d4312de2e --- /dev/null +++ b/src/new-frontend/src/store/user-store.tsx @@ -0,0 +1,19 @@ +import { create } from "zustand"; +import { UserOut, UsersService } from "../client"; + +interface UserStore { + user: UserOut | null; + getUser: () => Promise; + resetUser: () => void; +} + +export const useUserStore = create((set) => ({ + user: null, + getUser: async () => { + const user = await UsersService.readUserMe(); + set({ user }); + }, + resetUser: () => { + set({ user: null }); + } +})); \ No newline at end of file diff --git a/src/new-frontend/src/store/users-store.tsx b/src/new-frontend/src/store/users-store.tsx new file mode 100644 index 0000000000..5a0af29e9b --- /dev/null +++ b/src/new-frontend/src/store/users-store.tsx @@ -0,0 +1,21 @@ +import { create } from "zustand"; +import { UserCreate, UserOut, UsersService } from "../client"; + +interface UsersStore { + users: UserOut[]; + getUsers: () => Promise; + addUser: (user: UserCreate) => Promise; +} + +export const useUsersStore = create((set) => ({ + users: [], + getUsers: async () => { + const usersResponse = await UsersService.readUsers({ skip: 0, limit: 10 }); + set({ users: usersResponse }); + }, + addUser: async (user: UserCreate) => { + const userResponse = await UsersService.createUser({ requestBody: user }); + set((state) => ({ users: [...state.users, userResponse] })); + }, + // TODO: Add delete user +})) \ No newline at end of file From cfdef65be7f2a18bbe88e67f44e4e25c4c7c35b7 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:46:51 -0500 Subject: [PATCH 166/771] =?UTF-8?q?=E2=9C=A8=20Add=20new=20pages,=20compon?= =?UTF-8?q?ents,=20panels,=20modals,=20and=20theme;=20refactor=20and=20imp?= =?UTF-8?q?rovements=20in=20existing=20components=20(#593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- src/new-frontend/src/App.tsx | 56 +++++++++-- .../src/components/ActionsMenu.tsx | 39 ++++++++ src/new-frontend/src/components/Navbar.tsx | 30 ++++++ src/new-frontend/src/components/Sidebar.tsx | 4 +- .../src/components/SidebarItems.tsx | 22 +++-- src/new-frontend/src/components/UserInfo.tsx | 27 ++---- src/new-frontend/src/pages/auth/Login.tsx | 91 +++++++++--------- src/new-frontend/src/pages/main/Admin.tsx | 89 +++++++++++++++++ src/new-frontend/src/pages/main/Dashboard.tsx | 24 +++++ src/new-frontend/src/pages/main/Items.tsx | 78 +++++++++++++++ src/new-frontend/src/pages/main/Profile.tsx | 49 ++++++++++ .../src/pages/modals/CreateItem.tsx | 89 +++++++++++++++++ .../src/pages/modals/CreateUser.tsx | 96 +++++++++++++++++++ .../src/pages/modals/DeleteAlert.tsx | 68 +++++++++++++ .../src/pages/modals/EditItem.tsx | 48 ++++++++++ .../src/pages/modals/EditUser.tsx | 60 ++++++++++++ .../src/pages/panels/Appearance.tsx | 32 +++++++ .../src/pages/panels/ChangePassword.tsx | 34 +++++++ .../src/pages/panels/DeleteAccount.tsx | 23 +++++ .../src/pages/panels/UserInformation.tsx | 49 ++++++++++ 20 files changed, 927 insertions(+), 81 deletions(-) create mode 100644 src/new-frontend/src/components/ActionsMenu.tsx create mode 100644 src/new-frontend/src/components/Navbar.tsx create mode 100644 src/new-frontend/src/pages/main/Admin.tsx create mode 100644 src/new-frontend/src/pages/main/Dashboard.tsx create mode 100644 src/new-frontend/src/pages/main/Items.tsx create mode 100644 src/new-frontend/src/pages/main/Profile.tsx create mode 100644 src/new-frontend/src/pages/modals/CreateItem.tsx create mode 100644 src/new-frontend/src/pages/modals/CreateUser.tsx create mode 100644 src/new-frontend/src/pages/modals/DeleteAlert.tsx create mode 100644 src/new-frontend/src/pages/modals/EditItem.tsx create mode 100644 src/new-frontend/src/pages/modals/EditUser.tsx create mode 100644 src/new-frontend/src/pages/panels/Appearance.tsx create mode 100644 src/new-frontend/src/pages/panels/ChangePassword.tsx create mode 100644 src/new-frontend/src/pages/panels/DeleteAccount.tsx create mode 100644 src/new-frontend/src/pages/panels/UserInformation.tsx diff --git a/src/new-frontend/src/App.tsx b/src/new-frontend/src/App.tsx index 958baf13ca..6bbd728860 100644 --- a/src/new-frontend/src/App.tsx +++ b/src/new-frontend/src/App.tsx @@ -1,19 +1,57 @@ -import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; import Layout from './pages/Layout'; import Login from './pages/auth/Login'; import RecoverPassword from './pages/auth/RecoverPassword'; +import Admin from './pages/main/Admin'; +import Dashboard from './pages/main/Dashboard'; +import Items from './pages/main/Items'; +import Profile from './pages/main/Profile'; +import { ChakraProvider, extendTheme } from '@chakra-ui/react'; + +// Theme +const theme = extendTheme({ + colors: { + ui: { + main: "#009688", + secondary: "#EDF2F7", + success: '#48BB78', + danger: '#E53E3E', + } + }, + components: { + Tabs: { + variants: { + enclosed: { + tab: { + _selected: { + color: 'ui.main', + }, + }, + }, + }, + }, + }, +}); function App() { return ( - - - } /> - } /> - }> - - - + <> + + + + } /> + } /> + }> + } /> + } /> + } /> + } /> + + + + + ) } diff --git a/src/new-frontend/src/components/ActionsMenu.tsx b/src/new-frontend/src/components/ActionsMenu.tsx new file mode 100644 index 0000000000..1d14c47b30 --- /dev/null +++ b/src/new-frontend/src/components/ActionsMenu.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +import { Button, Menu, MenuButton, MenuItem, MenuList, useDisclosure } from '@chakra-ui/react'; +import { BsThreeDotsVertical } from 'react-icons/bs'; +import { FiTrash, FiEdit } from 'react-icons/fi'; + +import Delete from '../pages/modals/DeleteAlert'; +import EditUser from '../pages/modals/EditUser'; +import EditItem from '../pages/modals/EditItem'; + +interface ActionsMenuProps { + type: string; + id: number; +} + +const ActionsMenu: React.FC = ({ type, id }) => { + const editUserModal = useDisclosure(); + const deleteModal = useDisclosure(); + + return ( + <> + + } variant="unstyled"> + + + }>Edit {type} + } color="ui.danger">Delete {type} + + { + type === "User" ? + : + } + + + + ); +}; + +export default ActionsMenu; diff --git a/src/new-frontend/src/components/Navbar.tsx b/src/new-frontend/src/components/Navbar.tsx new file mode 100644 index 0000000000..1e347a9387 --- /dev/null +++ b/src/new-frontend/src/components/Navbar.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import { Button, Flex, Icon, useDisclosure } from '@chakra-ui/react'; +import { FaPlus } from "react-icons/fa"; + +import CreateItem from '../pages/modals/CreateItem'; +import CreateUser from '../pages/modals/CreateUser'; + +interface NavbarProps { + type: string; +} + +const Navbar: React.FC = ({ type }) => { + const createUserModal = useDisclosure(); + const createItemModal = useDisclosure(); + + return ( + <> + + + + + + + ); +}; + +export default Navbar; diff --git a/src/new-frontend/src/components/Sidebar.tsx b/src/new-frontend/src/components/Sidebar.tsx index a67623076f..d483a8c8e8 100644 --- a/src/new-frontend/src/components/Sidebar.tsx +++ b/src/new-frontend/src/components/Sidebar.tsx @@ -17,7 +17,7 @@ const Sidebar: React.FC = () => { } /> - + @@ -33,7 +33,7 @@ const Sidebar: React.FC = () => { {/* Desktop */} - + Logo diff --git a/src/new-frontend/src/components/SidebarItems.tsx b/src/new-frontend/src/components/SidebarItems.tsx index 7790111f80..e0f9281369 100644 --- a/src/new-frontend/src/components/SidebarItems.tsx +++ b/src/new-frontend/src/components/SidebarItems.tsx @@ -1,14 +1,15 @@ import React from 'react'; import { Flex, Icon, Text } from '@chakra-ui/react'; -import { FiBriefcase, FiHome, FiLogOut, FiUser, FiUsers } from 'react-icons/fi'; -import { Link } from 'react-router-dom'; +import { FiBriefcase, FiHome, FiLogOut, FiSettings, FiUsers } from 'react-icons/fi'; +import { Link, useNavigate } from 'react-router-dom'; + const items = [ { icon: FiHome, title: 'Dashboard', path: "/" }, - { icon: FiUser, title: 'Profile', path: "/profile" }, { icon: FiBriefcase, title: 'Items', path: "/items" }, { icon: FiUsers, title: 'Admin', path: "/admin" }, + { icon: FiSettings, title: 'User Settings', path: "/settings" }, { icon: FiLogOut, title: 'Log out' } ]; @@ -17,15 +18,24 @@ interface SidebarItemsProps { } const SidebarItems: React.FC = ({ onClose }) => { + const navigate = useNavigate(); + + const handleLogout = async () => { + localStorage.removeItem("access_token"); + navigate("/login"); + // TODO: reset all Zustand states + }; + const listItems = items.map((item) => ( + }} onClick={item.title === 'Log out' ? handleLogout : onClose}> - - + + {item.title} + diff --git a/src/new-frontend/src/components/UserInfo.tsx b/src/new-frontend/src/components/UserInfo.tsx index 3cf059b3f2..ddea6d002c 100644 --- a/src/new-frontend/src/components/UserInfo.tsx +++ b/src/new-frontend/src/components/UserInfo.tsx @@ -1,33 +1,22 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Avatar, Flex, Skeleton, Text } from '@chakra-ui/react'; import { FaUserAstronaut } from 'react-icons/fa'; -import { UserOut, UsersService } from '../client'; +import { useUserStore } from '../store/user-store'; -const UserInfo: React.FC = () => { - const [userData, setUserData] = useState(); - useEffect(() => { - const fetchUserData = async () => { - try { - const userResponse = await UsersService.readUserMe(); - setUserData(userResponse); - } catch (error) { - // TODO: Handle error to give feedback to the user - console.error(error); - } - }; - fetchUserData(); - }, []); +const UserInfo: React.FC = () => { + const { user } = useUserStore(); + return ( <> - {userData ? ( + {user ? ( - } size='sm' alignSelf="center" /> + } size='sm' alignSelf="center" /> {/* TODO: Conditional tooltip based on email length */} - {userData.email} + {user.email} ) : diff --git a/src/new-frontend/src/pages/auth/Login.tsx b/src/new-frontend/src/pages/auth/Login.tsx index 112ae1cea8..53e01ed224 100644 --- a/src/new-frontend/src/pages/auth/Login.tsx +++ b/src/new-frontend/src/pages/auth/Login.tsx @@ -10,10 +10,9 @@ import { LoginService } from "../../client"; import { Body_login_login_access_token as AccessToken } from "../../client/models/Body_login_login_access_token"; const Login: React.FC = () => { - const [show, setShow] = useBoolean(); +const [show, setShow] = useBoolean(); const navigate = useNavigate(); const { register, handleSubmit } = useForm(); - const onSubmit: SubmitHandler = async (data) => { const response = await LoginService.loginAccessToken({ formData: data, @@ -23,49 +22,51 @@ const Login: React.FC = () => { }; return ( - - FastAPI logo - - - - - - - - - {show ? : } - - - -
- - Forgot password? - -
-
- -
+ <> + + FastAPI logo + + + + + + + + + {show ? : } + + + +
+ + Forgot password? + +
+
+ +
+ ); }; diff --git a/src/new-frontend/src/pages/main/Admin.tsx b/src/new-frontend/src/pages/main/Admin.tsx new file mode 100644 index 0000000000..70fd5eb88c --- /dev/null +++ b/src/new-frontend/src/pages/main/Admin.tsx @@ -0,0 +1,89 @@ +import React, { useEffect, useState } from 'react'; + +import { Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr, useToast } from '@chakra-ui/react'; + +import ActionsMenu from '../../components/ActionsMenu'; +import Navbar from '../../components/Navbar'; +import { useUsersStore } from '../../store/users-store'; + +const Admin: React.FC = () => { + const toast = useToast(); + const [isLoading, setIsLoading] = useState(false); + const { users, getUsers } = useUsersStore(); + + useEffect(() => { + const fetchUsers = async () => { + try { + setIsLoading(true); + await getUsers(); + setIsLoading(false); + } catch (err) { + setIsLoading(false); + toast({ + title: 'Something went wrong.', + description: 'Failed to fetch users. Please try again.', + status: 'error', + isClosable: true, + }); + } + } + fetchUsers(); + }, []) + + return ( + <> + {isLoading ? ( + // TODO: Add skeleton + + + + ) : ( + users && + + + User Management + + + + + + + + + + + + + + {users.map((user) => ( + + + + + + + + ))} + +
Full nameEmailRoleStatus
{user.full_name || "N/A"}{user.email}{user.is_superuser ? "Superuser" : "User"} + + + {user.is_active ? "Active" : "Inactive"} + + + +
+
+
+ )} + + ) +} + +export default Admin; diff --git a/src/new-frontend/src/pages/main/Dashboard.tsx b/src/new-frontend/src/pages/main/Dashboard.tsx new file mode 100644 index 0000000000..3c616a7038 --- /dev/null +++ b/src/new-frontend/src/pages/main/Dashboard.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import { Box, Text } from '@chakra-ui/react'; + +import { useUserStore } from '../../store/user-store'; + + +const Dashboard: React.FC = () => { + const { user } = useUserStore(); + + return ( + <> + {user ? ( + + Hi, {user.full_name || user.email} 👋🏼 + Welcome back, nice to see you again! + + ) : null} + + + ) +} + +export default Dashboard; \ No newline at end of file diff --git a/src/new-frontend/src/pages/main/Items.tsx b/src/new-frontend/src/pages/main/Items.tsx new file mode 100644 index 0000000000..7e2a2fb314 --- /dev/null +++ b/src/new-frontend/src/pages/main/Items.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; + +import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr, useToast } from '@chakra-ui/react'; + +import ActionsMenu from '../../components/ActionsMenu'; +import Navbar from '../../components/Navbar'; +import { useItemsStore } from '../../store/items-store'; + + +const Items: React.FC = () => { + const toast = useToast(); + const [isLoading, setIsLoading] = useState(false); + const { items, getItems } = useItemsStore(); + + useEffect(() => { + const fetchItems = async () => { + try { + setIsLoading(true); + await getItems(); + setIsLoading(false); + } catch (err) { + setIsLoading(false); + toast({ + title: 'Something went wrong.', + description: 'Failed to fetch items. Please try again.', + status: 'error', + isClosable: true, + }); + } + } + fetchItems(); + }, []) + + + return ( + <> + {isLoading ? ( + // TODO: Add skeleton + + + + ) : ( + items && + + + Items Management + + + + + + + + + + + + + {items.map((item) => ( + + + + + + + ))} + +
IDTitleDescription
{item.id}{item.title}{item.description || "N/A"} + +
+
+
+ )} + + ) +} + +export default Items; \ No newline at end of file diff --git a/src/new-frontend/src/pages/main/Profile.tsx b/src/new-frontend/src/pages/main/Profile.tsx new file mode 100644 index 0000000000..4ef140f868 --- /dev/null +++ b/src/new-frontend/src/pages/main/Profile.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import { Container, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; + +import Appearance from '../panels/Appearance'; +import ChangePassword from '../panels/ChangePassword'; +import DeleteAccount from '../panels/DeleteAccount'; +import UserInformation from '../panels/UserInformation'; + + + +const Profile: React.FC = () => { + + return ( + <> + + + User Settings + + + + Profile + Password + Appearance + Danger zone + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Profile; + diff --git a/src/new-frontend/src/pages/modals/CreateItem.tsx b/src/new-frontend/src/pages/modals/CreateItem.tsx new file mode 100644 index 0000000000..d3574353a0 --- /dev/null +++ b/src/new-frontend/src/pages/modals/CreateItem.tsx @@ -0,0 +1,89 @@ +import React, { useState } from 'react'; + +import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useToast } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +import { ItemCreate } from '../../client'; +import { useItemsStore } from '../../store/items-store'; + +interface CreateItemProps { + isOpen: boolean; + onClose: () => void; +} + +const CreateItem: React.FC = ({ isOpen, onClose }) => { + const toast = useToast(); + const [isLoading, setIsLoading] = useState(false); + const { register, handleSubmit } = useForm(); + const { addItem } = useItemsStore(); + + const onSubmit: SubmitHandler = async (data) => { + try { + setIsLoading(true); + await addItem(data); + setIsLoading(false); + + toast({ + title: 'Success!', + description: 'Item created successfully.', + status: 'success', + isClosable: true, + }); + onClose(); + } catch (err) { + setIsLoading(false); + toast({ + title: 'Something went wrong.', + description: 'Failed to create item. Please try again.', + status: 'error', + isClosable: true, + }); + } + }; + + return ( + <> + + + + Create Item + + + + Title + + + + Description + + + + + + + + + + + + ); +}; + +export default CreateItem; diff --git a/src/new-frontend/src/pages/modals/CreateUser.tsx b/src/new-frontend/src/pages/modals/CreateUser.tsx new file mode 100644 index 0000000000..951f683438 --- /dev/null +++ b/src/new-frontend/src/pages/modals/CreateUser.tsx @@ -0,0 +1,96 @@ +import React, { useState } from 'react'; + +import { Box, Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Spinner, useToast } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +import { UserCreate } from '../../client'; +import { useUsersStore } from '../../store/users-store'; + +interface CreateUserProps { + isOpen: boolean; + onClose: () => void; +} + +const CreateUser: React.FC = ({ isOpen, onClose }) => { + const toast = useToast(); + const [isLoading, setIsLoading] = useState(false); + const { register, handleSubmit } = useForm(); + const { addUser } = useUsersStore(); + + const onSubmit: SubmitHandler = async (data) => { + try { + setIsLoading(true); + await addUser(data); + setIsLoading(false); + toast({ + title: 'Success!', + description: 'User created successfully.', + status: 'success', + isClosable: true, + }); + onClose(); + + } catch (err) { + setIsLoading(false); + toast({ + title: 'Something went wrong.', + description: 'Failed to create user. Please try again.', + status: 'error', + isClosable: true, + }); + } + } + + return ( + <> + + + + {/* TODO: Check passwords */} + Create User + + + + Email + + + + Full name + + + + Set Password + + + + Confirm Password + + + + + Is superuser? + + + Is active? + + + + + + + + + + + + ) +} + +export default CreateUser; \ No newline at end of file diff --git a/src/new-frontend/src/pages/modals/DeleteAlert.tsx b/src/new-frontend/src/pages/modals/DeleteAlert.tsx new file mode 100644 index 0000000000..9524ff330e --- /dev/null +++ b/src/new-frontend/src/pages/modals/DeleteAlert.tsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; + +import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; +import { useForm } from 'react-hook-form'; + +import { useItemsStore } from '../../store/items-store'; + +interface DeleteProps { + toDelete: string; + id: number + isOpen: boolean; + onClose: () => void; +} + +const Delete: React.FC = ({ toDelete, id, isOpen, onClose }) => { + const cancelRef = React.useRef(null); + const [isLoading, setIsLoading] = useState(false); + const { handleSubmit } = useForm(); + const { deleteItem } = useItemsStore(); + + const onSubmit = async () => { + try { + setIsLoading(true); + await deleteItem(id); + setIsLoading(false); + onClose(); + } catch (err) { + setIsLoading(false); + console.error(err); + + } + } + + return ( + <> + + + + + Delete {toDelete} + + + + Are you sure? You will not be able to undo this action. + + + + + + + + + + + ) +} + +export default Delete; \ No newline at end of file diff --git a/src/new-frontend/src/pages/modals/EditItem.tsx b/src/new-frontend/src/pages/modals/EditItem.tsx new file mode 100644 index 0000000000..cbd001f43c --- /dev/null +++ b/src/new-frontend/src/pages/modals/EditItem.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; + +interface EditItemProps { + isOpen: boolean; + onClose: () => void; +} + +const EditItem: React.FC = ({ isOpen, onClose }) => { + + return ( + <> + + + + Edit Item + + + + Item + + + + + Description + + + + + + + + + + + + ) +} + +export default EditItem; \ No newline at end of file diff --git a/src/new-frontend/src/pages/modals/EditUser.tsx b/src/new-frontend/src/pages/modals/EditUser.tsx new file mode 100644 index 0000000000..0434fe589b --- /dev/null +++ b/src/new-frontend/src/pages/modals/EditUser.tsx @@ -0,0 +1,60 @@ +import React from 'react'; + +import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure } from '@chakra-ui/react'; + +interface EditUserProps { + isOpen: boolean; + onClose: () => void; +} + +const EditUser: React.FC = ({ isOpen, onClose }) => { + + return ( + <> + + + + Edit User + + + + Email + + + + + Full name + + + + Set Password + + + + + Is superuser? + + + Is active? + + + + + + + + + + + + ) +} + +export default EditUser; \ No newline at end of file diff --git a/src/new-frontend/src/pages/panels/Appearance.tsx b/src/new-frontend/src/pages/panels/Appearance.tsx new file mode 100644 index 0000000000..af16abf6be --- /dev/null +++ b/src/new-frontend/src/pages/panels/Appearance.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import { Button, Container, Heading, Radio, RadioGroup, Stack } from '@chakra-ui/react'; + +const Appearance: React.FC = () => { + const [value, setValue] = React.useState('system'); + + return ( + <> + + + Appearance + + + + + Use system settings (default) + + + Light + + + Dark + + + + + + + ); +} +export default Appearance; \ No newline at end of file diff --git a/src/new-frontend/src/pages/panels/ChangePassword.tsx b/src/new-frontend/src/pages/panels/ChangePassword.tsx new file mode 100644 index 0000000000..f7437bf54f --- /dev/null +++ b/src/new-frontend/src/pages/panels/ChangePassword.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import { Box, Button, Container, FormControl, FormLabel, Heading, Input } from '@chakra-ui/react'; + +const ChangePassword: React.FC = () => { + + return ( + <> + + + Change Password + + + + Old password + + + + New password + + + + Confirm new password + + + + + + + ); +} +export default ChangePassword; \ No newline at end of file diff --git a/src/new-frontend/src/pages/panels/DeleteAccount.tsx b/src/new-frontend/src/pages/panels/DeleteAccount.tsx new file mode 100644 index 0000000000..1616b1d984 --- /dev/null +++ b/src/new-frontend/src/pages/panels/DeleteAccount.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { Button, Container, Heading, Text } from '@chakra-ui/react'; + +const DeleteAccount: React.FC = () => { + + return ( + <> + + + Delete Account + + + Are you sure you want to delete your account? This action cannot be undone. + + + + + ); +} +export default DeleteAccount; \ No newline at end of file diff --git a/src/new-frontend/src/pages/panels/UserInformation.tsx b/src/new-frontend/src/pages/panels/UserInformation.tsx new file mode 100644 index 0000000000..f7673ed007 --- /dev/null +++ b/src/new-frontend/src/pages/panels/UserInformation.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; + +import { Button, Container, FormControl, FormLabel, Heading, Input, Text } from '@chakra-ui/react'; + +import { useUserStore } from '../../store/user-store'; + +const UserInformation: React.FC = () => { + const [editMode, setEditMode] = useState(false); + const { user } = useUserStore(); + + + const toggleEditMode = () => { + setEditMode(!editMode); + }; + + return ( + <> + + + User Information + + + Full name + { + editMode ? + : + + {user?.full_name || "N/A"} + + } + + + Email + { + editMode ? + : + + {user?.email || "N/A"} + + } + + + + + ); +} +export default UserInformation; \ No newline at end of file From 01b2fe7961a7163836ccfaced42194564816b251 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:25:58 -0500 Subject: [PATCH 167/771] =?UTF-8?q?=E2=9C=A8=20Add=20delete=5Fuser;=20refa?= =?UTF-8?q?ctor=20delete=5Fitem=20(#594)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/app/api/api_v1/endpoints/items.py | 8 +++---- .../app/app/api/api_v1/endpoints/users.py | 22 +++++++++++++++++ .../src/client/services/ItemsService.ts | 5 ++-- .../src/client/services/UsersService.ts | 24 +++++++++++++++++++ src/new-frontend/src/store/users-store.tsx | 6 ++++- 5 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/backend/app/app/api/api_v1/endpoints/items.py b/src/backend/app/app/api/api_v1/endpoints/items.py index c72a28fd7f..e254cda368 100644 --- a/src/backend/app/app/api/api_v1/endpoints/items.py +++ b/src/backend/app/app/api/api_v1/endpoints/items.py @@ -4,7 +4,7 @@ from sqlmodel import select from app.api.deps import CurrentUser, SessionDep -from app.models import Item, ItemCreate, ItemOut, ItemUpdate +from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message router = APIRouter() @@ -78,8 +78,8 @@ def update_item( return item -@router.delete("/{id}", response_model=ItemOut) -def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> Any: +@router.delete("/{id}") +def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> Message: """ Delete an item. """ @@ -90,4 +90,4 @@ def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> Any: raise HTTPException(status_code=400, detail="Not enough permissions") session.delete(item) session.commit() - return item + return Message(message="Item deleted successfully") diff --git a/src/backend/app/app/api/api_v1/endpoints/users.py b/src/backend/app/app/api/api_v1/endpoints/users.py index f16a77d385..e3ec0fd34f 100644 --- a/src/backend/app/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/app/api/api_v1/endpoints/users.py @@ -11,6 +11,7 @@ ) from app.core.config import settings from app.models import ( + Message, User, UserCreate, UserCreateOpen, @@ -151,3 +152,24 @@ def update_user( # ) # user = crud.user.update(session, db_obj=user, obj_in=user_in) # return user + + +@router.delete("/{user_id}") +def delete_user( + session: SessionDep, current_user: CurrentUser, user_id: int +) -> Message: + """ + Delete a user. + """ + user = session.get(User, user_id) + if not user: + raise HTTPException(status_code=404, detail="User not found") + if not current_user.is_superuser: + raise HTTPException(status_code=400, detail="Not enough permissions") + if user == current_user: + raise HTTPException( + status_code=400, detail="Users are not allowed to delete themselves" + ) + session.delete(user) + session.commit() + return Message(message="User deleted successfully") diff --git a/src/new-frontend/src/client/services/ItemsService.ts b/src/new-frontend/src/client/services/ItemsService.ts index 2f9ed3cad7..0f46422dd3 100644 --- a/src/new-frontend/src/client/services/ItemsService.ts +++ b/src/new-frontend/src/client/services/ItemsService.ts @@ -5,6 +5,7 @@ import type { ItemCreate } from '../models/ItemCreate'; import type { ItemOut } from '../models/ItemOut'; import type { ItemUpdate } from '../models/ItemUpdate'; +import type { Message } from '../models/Message'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; @@ -113,14 +114,14 @@ requestBody: ItemUpdate, /** * Delete Item * Delete an item. - * @returns ItemOut Successful Response + * @returns Message Successful Response * @throws ApiError */ public static deleteItem({ id, }: { id: number, -}): CancelablePromise { +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/api/v1/items/{id}', diff --git a/src/new-frontend/src/client/services/UsersService.ts b/src/new-frontend/src/client/services/UsersService.ts index 759f94279f..b5911d69f6 100644 --- a/src/new-frontend/src/client/services/UsersService.ts +++ b/src/new-frontend/src/client/services/UsersService.ts @@ -2,6 +2,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { Message } from '../models/Message'; import type { UserCreate } from '../models/UserCreate'; import type { UserCreateOpen } from '../models/UserCreateOpen'; import type { UserOut } from '../models/UserOut'; @@ -169,4 +170,27 @@ requestBody: UserUpdate, }); } + /** + * Delete User + * Delete a user. + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteUser({ +userId, +}: { +userId: number, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/users/{user_id}', + path: { + 'user_id': userId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + } diff --git a/src/new-frontend/src/store/users-store.tsx b/src/new-frontend/src/store/users-store.tsx index 5a0af29e9b..eb3ee61e76 100644 --- a/src/new-frontend/src/store/users-store.tsx +++ b/src/new-frontend/src/store/users-store.tsx @@ -5,6 +5,7 @@ interface UsersStore { users: UserOut[]; getUsers: () => Promise; addUser: (user: UserCreate) => Promise; + deleteUser: (id: number) => Promise; } export const useUsersStore = create((set) => ({ @@ -17,5 +18,8 @@ export const useUsersStore = create((set) => ({ const userResponse = await UsersService.createUser({ requestBody: user }); set((state) => ({ users: [...state.users, userResponse] })); }, - // TODO: Add delete user + deleteUser: async (id: number) => { + await UsersService.deleteUser({ userId: id }); + set((state) => ({ users: state.users.filter((user) => user.id !== id) })); + } })) \ No newline at end of file From befed277c8d9645e8e8b53812f4d9d90703ad275 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Tue, 13 Feb 2024 15:06:57 -0500 Subject: [PATCH 168/771] =?UTF-8?q?=E2=9C=A8=20Add=20`Not=20Found`=20page?= =?UTF-8?q?=20(#595)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/src/App.tsx | 5 ++++- src/new-frontend/src/pages/NotFound.tsx | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/new-frontend/src/pages/NotFound.tsx diff --git a/src/new-frontend/src/App.tsx b/src/new-frontend/src/App.tsx index 6bbd728860..e9d621683b 100644 --- a/src/new-frontend/src/App.tsx +++ b/src/new-frontend/src/App.tsx @@ -1,13 +1,15 @@ import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; +import { ChakraProvider, extendTheme } from '@chakra-ui/react'; + import Layout from './pages/Layout'; +import NotFound from './pages/NotFound'; import Login from './pages/auth/Login'; import RecoverPassword from './pages/auth/RecoverPassword'; import Admin from './pages/main/Admin'; import Dashboard from './pages/main/Dashboard'; import Items from './pages/main/Items'; import Profile from './pages/main/Profile'; -import { ChakraProvider, extendTheme } from '@chakra-ui/react'; // Theme const theme = extendTheme({ @@ -48,6 +50,7 @@ function App() { } /> } /> + } /> diff --git a/src/new-frontend/src/pages/NotFound.tsx b/src/new-frontend/src/pages/NotFound.tsx new file mode 100644 index 0000000000..b6f5dee4eb --- /dev/null +++ b/src/new-frontend/src/pages/NotFound.tsx @@ -0,0 +1,18 @@ +import { Button, Container, Text } from "@chakra-ui/react"; + +import { Link } from "react-router-dom"; + +const NotFound = () => ( + <> + + 404 + Houston, we have a problem. + It looks like the page you're looking for doesn't exist. + + + +); + +export default NotFound; From 197119931e0dfd863d85311937cb859539f270b8 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 15 Feb 2024 17:17:26 -0500 Subject: [PATCH 169/771] =?UTF-8?q?=E2=9C=A8=20Migrate=20to=20RouterProvid?= =?UTF-8?q?er=20and=20other=20refactors=20(#598)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- src/new-frontend/src/App.css | 42 ------------ src/new-frontend/src/App.tsx | 61 ----------------- .../src/assets/images/fastapi-logo.png | Bin 17680 -> 0 bytes .../src/assets/images/fastapi-logo.svg | 51 ++++++++++++++ .../src/assets/images/favicon.png | Bin 412 -> 5043 bytes .../src/components/ActionsMenu.tsx | 6 +- src/new-frontend/src/components/Navbar.tsx | 30 +++++---- src/new-frontend/src/components/Sidebar.tsx | 25 ++++--- .../src/components/SidebarItems.tsx | 47 +++++++------ src/new-frontend/src/components/UserInfo.tsx | 29 -------- src/new-frontend/src/components/UserMenu.tsx | 45 +++++++++++++ src/new-frontend/src/main.tsx | 38 +++++++++-- .../CreateItem.tsx => modals/AddItem.tsx} | 28 ++++---- .../CreateUser.tsx => modals/AddUser.tsx} | 25 +++---- .../src/{pages => }/modals/DeleteAlert.tsx | 35 +++++++--- .../src/{pages => }/modals/EditItem.tsx | 4 +- .../src/{pages => }/modals/EditUser.tsx | 10 +-- .../src/pages/{main => }/Admin.tsx | 19 +++--- src/new-frontend/src/pages/Dashboard.tsx | 22 ++++++ src/new-frontend/src/pages/ErrorPage.tsx | 26 ++++++++ .../src/pages/{main => }/Items.tsx | 17 +++-- src/new-frontend/src/pages/Layout.tsx | 15 ----- .../src/pages/{auth => }/Login.tsx | 10 +-- src/new-frontend/src/pages/NotFound.tsx | 18 ----- .../src/pages/RecoverPassword.tsx | 63 ++++++++++++++++++ src/new-frontend/src/pages/Root.tsx | 42 ++++++++++++ .../{main/Profile.tsx => UserSettings.tsx} | 6 +- src/new-frontend/src/pages/main/Dashboard.tsx | 24 ------- .../src/{pages => }/panels/Appearance.tsx | 2 +- .../src/{pages => }/panels/ChangePassword.tsx | 2 +- .../src/{pages => }/panels/DeleteAccount.tsx | 2 +- .../{pages => }/panels/UserInformation.tsx | 4 +- src/new-frontend/src/theme.tsx | 37 ++++++++++ 33 files changed, 472 insertions(+), 313 deletions(-) delete mode 100644 src/new-frontend/src/App.css delete mode 100644 src/new-frontend/src/App.tsx delete mode 100644 src/new-frontend/src/assets/images/fastapi-logo.png create mode 100644 src/new-frontend/src/assets/images/fastapi-logo.svg delete mode 100644 src/new-frontend/src/components/UserInfo.tsx create mode 100644 src/new-frontend/src/components/UserMenu.tsx rename src/new-frontend/src/{pages/modals/CreateItem.tsx => modals/AddItem.tsx} (80%) rename src/new-frontend/src/{pages/modals/CreateUser.tsx => modals/AddUser.tsx} (81%) rename src/new-frontend/src/{pages => }/modals/DeleteAlert.tsx (60%) rename src/new-frontend/src/{pages => }/modals/EditItem.tsx (90%) rename src/new-frontend/src/{pages => }/modals/EditUser.tsx (86%) rename src/new-frontend/src/pages/{main => }/Admin.tsx (89%) create mode 100644 src/new-frontend/src/pages/Dashboard.tsx create mode 100644 src/new-frontend/src/pages/ErrorPage.tsx rename src/new-frontend/src/pages/{main => }/Items.tsx (88%) delete mode 100644 src/new-frontend/src/pages/Layout.tsx rename src/new-frontend/src/pages/{auth => }/Login.tsx (89%) delete mode 100644 src/new-frontend/src/pages/NotFound.tsx create mode 100644 src/new-frontend/src/pages/RecoverPassword.tsx create mode 100644 src/new-frontend/src/pages/Root.tsx rename src/new-frontend/src/pages/{main/Profile.tsx => UserSettings.tsx} (92%) delete mode 100644 src/new-frontend/src/pages/main/Dashboard.tsx rename src/new-frontend/src/{pages => }/panels/Appearance.tsx (91%) rename src/new-frontend/src/{pages => }/panels/ChangePassword.tsx (92%) rename src/new-frontend/src/{pages => }/panels/DeleteAccount.tsx (86%) rename src/new-frontend/src/{pages => }/panels/UserInformation.tsx (90%) create mode 100644 src/new-frontend/src/theme.tsx diff --git a/src/new-frontend/src/App.css b/src/new-frontend/src/App.css deleted file mode 100644 index b9d355df2a..0000000000 --- a/src/new-frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/new-frontend/src/App.tsx b/src/new-frontend/src/App.tsx deleted file mode 100644 index e9d621683b..0000000000 --- a/src/new-frontend/src/App.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; - -import { ChakraProvider, extendTheme } from '@chakra-ui/react'; - -import Layout from './pages/Layout'; -import NotFound from './pages/NotFound'; -import Login from './pages/auth/Login'; -import RecoverPassword from './pages/auth/RecoverPassword'; -import Admin from './pages/main/Admin'; -import Dashboard from './pages/main/Dashboard'; -import Items from './pages/main/Items'; -import Profile from './pages/main/Profile'; - -// Theme -const theme = extendTheme({ - colors: { - ui: { - main: "#009688", - secondary: "#EDF2F7", - success: '#48BB78', - danger: '#E53E3E', - } - }, - components: { - Tabs: { - variants: { - enclosed: { - tab: { - _selected: { - color: 'ui.main', - }, - }, - }, - }, - }, - }, -}); - -function App() { - return ( - <> - - - - } /> - } /> - }> - } /> - } /> - } /> - } /> - - } /> - - - - - ) -} - -export default App diff --git a/src/new-frontend/src/assets/images/fastapi-logo.png b/src/new-frontend/src/assets/images/fastapi-logo.png deleted file mode 100644 index 57d9eec137c0458dd83539835397fdd299e650ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17680 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 XuqwmUh9A!VCZQy!E?XsS^7a1$wHKYW diff --git a/src/new-frontend/src/assets/images/fastapi-logo.svg b/src/new-frontend/src/assets/images/fastapi-logo.svg new file mode 100644 index 0000000000..d3dad4bec8 --- /dev/null +++ b/src/new-frontend/src/assets/images/fastapi-logo.svg @@ -0,0 +1,51 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/src/new-frontend/src/assets/images/favicon.png b/src/new-frontend/src/assets/images/favicon.png index b3dcdd3090e52a3d94affb00a40df2fe8a56d4ba..e5b7c3ada7d093d237711560e03f6a841a4a41b2 100644 GIT binary patch literal 5043 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 = ({ type }) => { - const createUserModal = useDisclosure(); - const createItemModal = useDisclosure(); + const addUserModal = useDisclosure(); + const addItemModal = useDisclosure(); return ( <> - - - - - + + +
); }; diff --git a/src/new-frontend/src/components/Sidebar.tsx b/src/new-frontend/src/components/Sidebar.tsx index d483a8c8e8..0d281520a6 100644 --- a/src/new-frontend/src/components/Sidebar.tsx +++ b/src/new-frontend/src/components/Sidebar.tsx @@ -1,15 +1,16 @@ import React from 'react'; -import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, useDisclosure } from '@chakra-ui/react'; +import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, useDisclosure, Text } from '@chakra-ui/react'; import { FiMenu } from 'react-icons/fi'; -import Logo from "../assets/images/fastapi-logo.png"; +import Logo from "../assets/images/fastapi-logo.svg"; import SidebarItems from './SidebarItems'; -import UserInfo from './UserInfo'; +import { useUserStore } from '../store/user-store'; const Sidebar: React.FC = () => { const { isOpen, onOpen, onClose } = useDisclosure(); + const { user } = useUserStore(); return ( <> @@ -20,12 +21,15 @@ const Sidebar: React.FC = () => { - + - Logo + Logo - + { + user?.email && + Logged in as: {user.email} + } @@ -33,12 +37,15 @@ const Sidebar: React.FC = () => { {/* Desktop */} - + - Logo + Logo - + { + user?.email && + Logged in as: {user.email} + } diff --git a/src/new-frontend/src/components/SidebarItems.tsx b/src/new-frontend/src/components/SidebarItems.tsx index e0f9281369..2282aaa3f0 100644 --- a/src/new-frontend/src/components/SidebarItems.tsx +++ b/src/new-frontend/src/components/SidebarItems.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import { Flex, Icon, Text } from '@chakra-ui/react'; -import { FiBriefcase, FiHome, FiLogOut, FiSettings, FiUsers } from 'react-icons/fi'; -import { Link, useNavigate } from 'react-router-dom'; - +import { Box, Flex, Icon, Text } from '@chakra-ui/react'; +import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi'; +import { Link, useLocation } from 'react-router-dom'; const items = [ { icon: FiHome, title: 'Dashboard', path: "/" }, { icon: FiBriefcase, title: 'Items', path: "/items" }, { icon: FiUsers, title: 'Admin', path: "/admin" }, { icon: FiSettings, title: 'User Settings', path: "/settings" }, - { icon: FiLogOut, title: 'Log out' } ]; interface SidebarItemsProps { @@ -18,32 +16,33 @@ interface SidebarItemsProps { } const SidebarItems: React.FC = ({ onClose }) => { - const navigate = useNavigate(); - - const handleLogout = async () => { - localStorage.removeItem("access_token"); - navigate("/login"); - // TODO: reset all Zustand states - }; + const location = useLocation(); const listItems = items.map((item) => ( - - - - - {item.title} - - - + + + {item.title} )); return ( <> - {listItems} + + {listItems} + + ); }; diff --git a/src/new-frontend/src/components/UserInfo.tsx b/src/new-frontend/src/components/UserInfo.tsx deleted file mode 100644 index ddea6d002c..0000000000 --- a/src/new-frontend/src/components/UserInfo.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -import { Avatar, Flex, Skeleton, Text } from '@chakra-ui/react'; -import { FaUserAstronaut } from 'react-icons/fa'; - -import { useUserStore } from '../store/user-store'; - - -const UserInfo: React.FC = () => { - const { user } = useUserStore(); - - - return ( - <> - {user ? ( - - } size='sm' alignSelf="center" /> - {/* TODO: Conditional tooltip based on email length */} - {user.email} - - ) : - - } - - ); - -} - -export default UserInfo; \ No newline at end of file diff --git a/src/new-frontend/src/components/UserMenu.tsx b/src/new-frontend/src/components/UserMenu.tsx new file mode 100644 index 0000000000..224af6012e --- /dev/null +++ b/src/new-frontend/src/components/UserMenu.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +import { IconButton } from '@chakra-ui/button'; +import { Box } from '@chakra-ui/layout'; +import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu'; +import { FaUserAstronaut } from 'react-icons/fa'; +import { FiLogOut, FiUser } from 'react-icons/fi'; +import { useNavigate } from 'react-router'; +import { Link } from 'react-router-dom'; + +const UserMenu: React.FC = () => { + const navigate = useNavigate(); + + const handleLogout = async () => { + localStorage.removeItem("access_token"); + navigate("/login"); + // TODO: reset all Zustand states + }; + + return ( + <> + + + } + bg="ui.main" + isRound + /> + + } as={Link} to="settings"> + My profile + + } onClick={handleLogout} color="ui.danger"> + Log out + + + + + + ); +}; + +export default UserMenu; diff --git a/src/new-frontend/src/main.tsx b/src/new-frontend/src/main.tsx index f9ebbd94f0..146cbb5494 100644 --- a/src/new-frontend/src/main.tsx +++ b/src/new-frontend/src/main.tsx @@ -1,20 +1,50 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import { ChakraProvider } from '@chakra-ui/react'; +import { ChakraProvider } from '@chakra-ui/provider'; +import { createStandaloneToast } from '@chakra-ui/toast'; +import { RouterProvider, createBrowserRouter } from 'react-router-dom'; -import App from './App'; import { OpenAPI } from './client'; +import Admin from './pages/Admin'; +import Dashboard from './pages/Dashboard'; +import ErrorPage from './pages/ErrorPage'; +import Items from './pages/Items'; +import Login from './pages/Login'; +import RecoverPassword from './pages/RecoverPassword'; +import Root from './pages/Root'; +import Profile from './pages/UserSettings'; +import theme from './theme'; + OpenAPI.BASE = import.meta.env.VITE_API_URL; OpenAPI.TOKEN = async () => { return localStorage.getItem('access_token') || ''; } +const router = createBrowserRouter([ + { + path: '/', + element: , + errorElement: , + children: [ + { path: '/', element: }, + { path: 'items', element: }, + { path: 'admin', element: }, + { path: 'settings', element: }, + ], + }, + { path: 'login', element: , errorElement: , }, + { path: 'recover-password', element: , errorElement: , }, +]); + +const { ToastContainer } = createStandaloneToast(); + ReactDOM.createRoot(document.getElementById('root')!).render( - - + + + , ) diff --git a/src/new-frontend/src/pages/modals/CreateItem.tsx b/src/new-frontend/src/modals/AddItem.tsx similarity index 80% rename from src/new-frontend/src/pages/modals/CreateItem.tsx rename to src/new-frontend/src/modals/AddItem.tsx index d3574353a0..c7967019cc 100644 --- a/src/new-frontend/src/pages/modals/CreateItem.tsx +++ b/src/new-frontend/src/modals/AddItem.tsx @@ -3,41 +3,41 @@ import React, { useState } from 'react'; import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useToast } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { ItemCreate } from '../../client'; -import { useItemsStore } from '../../store/items-store'; +import { ItemCreate } from '../client'; +import { useItemsStore } from '../store/items-store'; -interface CreateItemProps { +interface AddItemProps { isOpen: boolean; onClose: () => void; } -const CreateItem: React.FC = ({ isOpen, onClose }) => { +const AddItem: React.FC = ({ isOpen, onClose }) => { const toast = useToast(); const [isLoading, setIsLoading] = useState(false); - const { register, handleSubmit } = useForm(); + const { register, handleSubmit, reset } = useForm(); const { addItem } = useItemsStore(); const onSubmit: SubmitHandler = async (data) => { + setIsLoading(true); try { - setIsLoading(true); await addItem(data); - setIsLoading(false); - toast({ title: 'Success!', description: 'Item created successfully.', status: 'success', isClosable: true, }); + reset(); onClose(); } catch (err) { - setIsLoading(false); toast({ title: 'Something went wrong.', description: 'Failed to create item. Please try again.', status: 'error', isClosable: true, }); + } finally { + setIsLoading(false); } }; @@ -51,7 +51,7 @@ const CreateItem: React.FC = ({ isOpen, onClose }) => { > - Create Item + Add Item @@ -59,7 +59,7 @@ const CreateItem: React.FC = ({ isOpen, onClose }) => { @@ -67,13 +67,13 @@ const CreateItem: React.FC = ({ isOpen, onClose }) => { - diff --git a/src/new-frontend/src/pages/main/Admin.tsx b/src/new-frontend/src/pages/Admin.tsx similarity index 89% rename from src/new-frontend/src/pages/main/Admin.tsx rename to src/new-frontend/src/pages/Admin.tsx index 70fd5eb88c..17b410fabb 100644 --- a/src/new-frontend/src/pages/main/Admin.tsx +++ b/src/new-frontend/src/pages/Admin.tsx @@ -2,9 +2,9 @@ import React, { useEffect, useState } from 'react'; import { Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr, useToast } from '@chakra-ui/react'; -import ActionsMenu from '../../components/ActionsMenu'; -import Navbar from '../../components/Navbar'; -import { useUsersStore } from '../../store/users-store'; +import ActionsMenu from '../components/ActionsMenu'; +import Navbar from '../components/Navbar'; +import { useUsersStore } from '../store/users-store'; const Admin: React.FC = () => { const toast = useToast(); @@ -13,21 +13,23 @@ const Admin: React.FC = () => { useEffect(() => { const fetchUsers = async () => { + setIsLoading(true); try { - setIsLoading(true); await getUsers(); - setIsLoading(false); } catch (err) { - setIsLoading(false); toast({ title: 'Something went wrong.', description: 'Failed to fetch users. Please try again.', status: 'error', isClosable: true, }); + } finally { + setIsLoading(false); } } - fetchUsers(); + if (users.length === 0) { + fetchUsers(); + } }, []) return ( @@ -35,7 +37,7 @@ const Admin: React.FC = () => { {isLoading ? ( // TODO: Add skeleton - + ) : ( users && @@ -52,6 +54,7 @@ const Admin: React.FC = () => { Email Role Status + Actions diff --git a/src/new-frontend/src/pages/Dashboard.tsx b/src/new-frontend/src/pages/Dashboard.tsx new file mode 100644 index 0000000000..e0a5e07a5b --- /dev/null +++ b/src/new-frontend/src/pages/Dashboard.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { Box, Text } from '@chakra-ui/react'; + +import { useUserStore } from '../store/user-store'; + + +const Dashboard: React.FC = () => { + const { user } = useUserStore(); + + return ( + <> + + Hi, {user?.full_name || user?.email} 👋🏼 + Welcome back, nice to see you again! + + + + ) +} + +export default Dashboard; \ No newline at end of file diff --git a/src/new-frontend/src/pages/ErrorPage.tsx b/src/new-frontend/src/pages/ErrorPage.tsx new file mode 100644 index 0000000000..f8bd796ec2 --- /dev/null +++ b/src/new-frontend/src/pages/ErrorPage.tsx @@ -0,0 +1,26 @@ +import { Button, Container, Text } from "@chakra-ui/react"; + +import { Link, useRouteError } from "react-router-dom"; + +const ErrorPage: React.FC = () => { + const error = useRouteError(); + console.log(error); + + return ( + <> + + Oops! + Houston, we have a problem. + An unexpected error has occurred. + {error.statusText || error.message} + + + + ); +} + +export default ErrorPage; + + diff --git a/src/new-frontend/src/pages/main/Items.tsx b/src/new-frontend/src/pages/Items.tsx similarity index 88% rename from src/new-frontend/src/pages/main/Items.tsx rename to src/new-frontend/src/pages/Items.tsx index 7e2a2fb314..bcf1c04dd6 100644 --- a/src/new-frontend/src/pages/main/Items.tsx +++ b/src/new-frontend/src/pages/Items.tsx @@ -2,9 +2,9 @@ import React, { useEffect, useState } from 'react'; import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr, useToast } from '@chakra-ui/react'; -import ActionsMenu from '../../components/ActionsMenu'; -import Navbar from '../../components/Navbar'; -import { useItemsStore } from '../../store/items-store'; +import ActionsMenu from '../components/ActionsMenu'; +import Navbar from '../components/Navbar'; +import { useItemsStore } from '../store/items-store'; const Items: React.FC = () => { @@ -14,21 +14,23 @@ const Items: React.FC = () => { useEffect(() => { const fetchItems = async () => { + setIsLoading(true); try { - setIsLoading(true); await getItems(); - setIsLoading(false); } catch (err) { - setIsLoading(false); toast({ title: 'Something went wrong.', description: 'Failed to fetch items. Please try again.', status: 'error', isClosable: true, }); + } finally { + setIsLoading(false); } } - fetchItems(); + if (items.length === 0) { + fetchItems(); + } }, []) @@ -53,6 +55,7 @@ const Items: React.FC = () => { ID Title Description + Actions diff --git a/src/new-frontend/src/pages/Layout.tsx b/src/new-frontend/src/pages/Layout.tsx deleted file mode 100644 index bbf1087ce6..0000000000 --- a/src/new-frontend/src/pages/Layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Outlet } from 'react-router-dom'; -import Sidebar from '../components/Sidebar'; - -import { Flex } from '@chakra-ui/react'; - -const Layout = () => { - return ( - - - - - ); -}; - -export default Layout; diff --git a/src/new-frontend/src/pages/auth/Login.tsx b/src/new-frontend/src/pages/Login.tsx similarity index 89% rename from src/new-frontend/src/pages/auth/Login.tsx rename to src/new-frontend/src/pages/Login.tsx index 53e01ed224..b9e89803e2 100644 --- a/src/new-frontend/src/pages/auth/Login.tsx +++ b/src/new-frontend/src/pages/Login.tsx @@ -5,12 +5,12 @@ import { Button, Center, Container, FormControl, Icon, Image, Input, InputGroup, import { SubmitHandler, useForm } from "react-hook-form"; import { Link as ReactRouterLink, useNavigate } from "react-router-dom"; -import Logo from "../../assets/images/fastapi-logo.png"; -import { LoginService } from "../../client"; -import { Body_login_login_access_token as AccessToken } from "../../client/models/Body_login_login_access_token"; +import Logo from "../assets/images/fastapi-logo.svg"; +import { LoginService } from "../client"; +import { Body_login_login_access_token as AccessToken } from "../client/models/Body_login_login_access_token"; const Login: React.FC = () => { -const [show, setShow] = useBoolean(); + const [show, setShow] = useBoolean(); const navigate = useNavigate(); const { register, handleSubmit } = useForm(); const onSubmit: SubmitHandler = async (data) => { @@ -62,7 +62,7 @@ const [show, setShow] = useBoolean(); - diff --git a/src/new-frontend/src/pages/NotFound.tsx b/src/new-frontend/src/pages/NotFound.tsx deleted file mode 100644 index b6f5dee4eb..0000000000 --- a/src/new-frontend/src/pages/NotFound.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Button, Container, Text } from "@chakra-ui/react"; - -import { Link } from "react-router-dom"; - -const NotFound = () => ( - <> - - 404 - Houston, we have a problem. - It looks like the page you're looking for doesn't exist. - - - -); - -export default NotFound; diff --git a/src/new-frontend/src/pages/RecoverPassword.tsx b/src/new-frontend/src/pages/RecoverPassword.tsx new file mode 100644 index 0000000000..4de1aba194 --- /dev/null +++ b/src/new-frontend/src/pages/RecoverPassword.tsx @@ -0,0 +1,63 @@ +import React from "react"; + +import { Button, Container, FormControl, Heading, Input, Text, useToast } from "@chakra-ui/react"; +import { SubmitHandler, useForm } from "react-hook-form"; + +import { LoginService } from "../client"; + +interface FormData { + email: string; +} + +const RecoverPassword: React.FC = () => { + const { register, handleSubmit } = useForm(); + const toast = useToast(); + + const onSubmit: SubmitHandler = async (data) => { + const response = await LoginService.recoverPassword({ + email: data.email, + }); + console.log(response); + + toast({ + title: "Email sent.", + description: "We sent an email with a link to get back into your account.", + status: "success", + isClosable: true, + }); + }; + + return ( + + + Password Recovery + + + + A password recovery email will be sent to the registered account. + + + + + + ); +}; + +export default RecoverPassword; diff --git a/src/new-frontend/src/pages/Root.tsx b/src/new-frontend/src/pages/Root.tsx new file mode 100644 index 0000000000..3ebcd9ddcb --- /dev/null +++ b/src/new-frontend/src/pages/Root.tsx @@ -0,0 +1,42 @@ +import { useEffect } from 'react'; + +import { Outlet } from 'react-router-dom'; +import Sidebar from '../components/Sidebar'; + +import { Flex, useToast } from '@chakra-ui/react'; +import { useUserStore } from '../store/user-store'; +import UserMenu from '../components/UserMenu'; + +const Root: React.FC = () => { + const toast = useToast(); + const { getUser } = useUserStore(); + + useEffect(() => { + const fetchUser = async () => { + const token = localStorage.getItem('access_token'); + if (token) { + try { + await getUser(); + } catch (err) { + toast({ + title: 'Something went wrong.', + description: 'Failed to fetch user. Please try again.', + status: 'error', + isClosable: true, + }); + } + } + } + fetchUser(); + }, []); + + return ( + + + + + + ); +}; + +export default Root; \ No newline at end of file diff --git a/src/new-frontend/src/pages/main/Profile.tsx b/src/new-frontend/src/pages/UserSettings.tsx similarity index 92% rename from src/new-frontend/src/pages/main/Profile.tsx rename to src/new-frontend/src/pages/UserSettings.tsx index 4ef140f868..a8156a4592 100644 --- a/src/new-frontend/src/pages/main/Profile.tsx +++ b/src/new-frontend/src/pages/UserSettings.tsx @@ -9,7 +9,7 @@ import UserInformation from '../panels/UserInformation'; -const Profile: React.FC = () => { +const UserSettings: React.FC = () => { return ( <> @@ -19,7 +19,7 @@ const Profile: React.FC = () => { - Profile + My profile Password Appearance Danger zone @@ -45,5 +45,5 @@ const Profile: React.FC = () => { ); }; -export default Profile; +export default UserSettings; diff --git a/src/new-frontend/src/pages/main/Dashboard.tsx b/src/new-frontend/src/pages/main/Dashboard.tsx deleted file mode 100644 index 3c616a7038..0000000000 --- a/src/new-frontend/src/pages/main/Dashboard.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -import { Box, Text } from '@chakra-ui/react'; - -import { useUserStore } from '../../store/user-store'; - - -const Dashboard: React.FC = () => { - const { user } = useUserStore(); - - return ( - <> - {user ? ( - - Hi, {user.full_name || user.email} 👋🏼 - Welcome back, nice to see you again! - - ) : null} - - - ) -} - -export default Dashboard; \ No newline at end of file diff --git a/src/new-frontend/src/pages/panels/Appearance.tsx b/src/new-frontend/src/panels/Appearance.tsx similarity index 91% rename from src/new-frontend/src/pages/panels/Appearance.tsx rename to src/new-frontend/src/panels/Appearance.tsx index af16abf6be..71caee6246 100644 --- a/src/new-frontend/src/pages/panels/Appearance.tsx +++ b/src/new-frontend/src/panels/Appearance.tsx @@ -24,7 +24,7 @@ const Appearance: React.FC = () => { - + ); diff --git a/src/new-frontend/src/pages/panels/ChangePassword.tsx b/src/new-frontend/src/panels/ChangePassword.tsx similarity index 92% rename from src/new-frontend/src/pages/panels/ChangePassword.tsx rename to src/new-frontend/src/panels/ChangePassword.tsx index f7437bf54f..1f1f056a20 100644 --- a/src/new-frontend/src/pages/panels/ChangePassword.tsx +++ b/src/new-frontend/src/panels/ChangePassword.tsx @@ -23,7 +23,7 @@ const ChangePassword: React.FC = () => { Confirm new password -
diff --git a/src/new-frontend/src/pages/panels/DeleteAccount.tsx b/src/new-frontend/src/panels/DeleteAccount.tsx similarity index 86% rename from src/new-frontend/src/pages/panels/DeleteAccount.tsx rename to src/new-frontend/src/panels/DeleteAccount.tsx index 1616b1d984..793605ccf5 100644 --- a/src/new-frontend/src/pages/panels/DeleteAccount.tsx +++ b/src/new-frontend/src/panels/DeleteAccount.tsx @@ -13,7 +13,7 @@ const DeleteAccount: React.FC = () => { Are you sure you want to delete your account? This action cannot be undone. - diff --git a/src/new-frontend/src/pages/panels/UserInformation.tsx b/src/new-frontend/src/panels/UserInformation.tsx similarity index 90% rename from src/new-frontend/src/pages/panels/UserInformation.tsx rename to src/new-frontend/src/panels/UserInformation.tsx index f7673ed007..13f6ba24fb 100644 --- a/src/new-frontend/src/pages/panels/UserInformation.tsx +++ b/src/new-frontend/src/panels/UserInformation.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Button, Container, FormControl, FormLabel, Heading, Input, Text } from '@chakra-ui/react'; -import { useUserStore } from '../../store/user-store'; +import { useUserStore } from '../store/user-store'; const UserInformation: React.FC = () => { const [editMode, setEditMode] = useState(false); @@ -39,7 +39,7 @@ const UserInformation: React.FC = () => { } - diff --git a/src/new-frontend/src/theme.tsx b/src/new-frontend/src/theme.tsx new file mode 100644 index 0000000000..cc62ba12ec --- /dev/null +++ b/src/new-frontend/src/theme.tsx @@ -0,0 +1,37 @@ +import { extendTheme } from "@chakra-ui/react" + +const theme = extendTheme({ + colors: { + ui: { + main: "#009688", + secondary: "#EDF2F7", + success: '#48BB78', + danger: '#E53E3E', + focus: 'red', + } + }, + components: { + Tabs: { + variants: { + enclosed: { + tab: { + _selected: { + color: 'ui.main', + }, + }, + }, + }, + }, + Input: { + baseStyle: { + field: { + _focus: { + borderColor: 'ui.focus', + }, + }, + }, + }, + }, +}); + +export default theme; \ No newline at end of file From a4bb823bdc25ff68d0e1e35fe2eb55b6573f38b6 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:38:00 -0500 Subject: [PATCH 170/771] =?UTF-8?q?=E2=9C=A8=20Add=20dark=20mode=20to=20ne?= =?UTF-8?q?w-frontend=20and=20conditional=20sidebar=20items=20(#600)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ActionsMenu.tsx | 2 +- src/new-frontend/src/components/Sidebar.tsx | 16 +++-- .../src/components/SidebarItems.tsx | 18 +++-- src/new-frontend/src/components/UserMenu.tsx | 2 +- src/new-frontend/src/modals/DeleteAlert.tsx | 2 +- .../src/modals/DeleteConfirmation.tsx | 72 +++++++++++++++++++ src/new-frontend/src/pages/Admin.tsx | 2 +- src/new-frontend/src/pages/Dashboard.tsx | 6 +- src/new-frontend/src/pages/Items.tsx | 2 +- .../src/pages/{Root.tsx => Layout.tsx} | 4 +- .../src/pages/RecoverPassword.tsx | 2 +- src/new-frontend/src/pages/UserSettings.tsx | 2 +- src/new-frontend/src/panels/Appearance.tsx | 14 ++-- .../src/panels/ChangePassword.tsx | 9 +-- src/new-frontend/src/panels/DeleteAccount.tsx | 8 ++- .../src/panels/UserInformation.tsx | 15 ++-- 16 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 src/new-frontend/src/modals/DeleteConfirmation.tsx rename src/new-frontend/src/pages/{Root.tsx => Layout.tsx} (95%) diff --git a/src/new-frontend/src/components/ActionsMenu.tsx b/src/new-frontend/src/components/ActionsMenu.tsx index ea7d8bd25b..2a3a2a7d58 100644 --- a/src/new-frontend/src/components/ActionsMenu.tsx +++ b/src/new-frontend/src/components/ActionsMenu.tsx @@ -20,7 +20,7 @@ const ActionsMenu: React.FC = ({ type, id }) => { return ( <> - } variant="unstyled"> + } variant="unstyled"> }>Edit {type} diff --git a/src/new-frontend/src/components/Sidebar.tsx b/src/new-frontend/src/components/Sidebar.tsx index 0d281520a6..8450fb0a21 100644 --- a/src/new-frontend/src/components/Sidebar.tsx +++ b/src/new-frontend/src/components/Sidebar.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, useDisclosure, Text } from '@chakra-ui/react'; +import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, useDisclosure, Text, useColorModeValue } from '@chakra-ui/react'; import { FiMenu } from 'react-icons/fi'; import Logo from "../assets/images/fastapi-logo.svg"; @@ -9,6 +9,10 @@ import { useUserStore } from '../store/user-store'; const Sidebar: React.FC = () => { + const bgColor = useColorModeValue("white", "#1a202c"); + const textColor = useColorModeValue("gray", "white"); + const secBgColor = useColorModeValue("ui.secondary", "#252d3d"); + const { isOpen, onOpen, onClose } = useDisclosure(); const { user } = useUserStore(); @@ -18,7 +22,7 @@ const Sidebar: React.FC = () => { } /> - + @@ -28,7 +32,7 @@ const Sidebar: React.FC = () => { { user?.email && - Logged in as: {user.email} + Logged in as: {user.email} } @@ -36,15 +40,15 @@ const Sidebar: React.FC = () => { {/* Desktop */} - - + + Logo { user?.email && - Logged in as: {user.email} + Logged in as: {user.email} } diff --git a/src/new-frontend/src/components/SidebarItems.tsx b/src/new-frontend/src/components/SidebarItems.tsx index 2282aaa3f0..19c1d93cef 100644 --- a/src/new-frontend/src/components/SidebarItems.tsx +++ b/src/new-frontend/src/components/SidebarItems.tsx @@ -1,13 +1,14 @@ import React from 'react'; -import { Box, Flex, Icon, Text } from '@chakra-ui/react'; +import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'; import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi'; import { Link, useLocation } from 'react-router-dom'; +import { useUserStore } from '../store/user-store'; + const items = [ { icon: FiHome, title: 'Dashboard', path: "/" }, { icon: FiBriefcase, title: 'Items', path: "/items" }, - { icon: FiUsers, title: 'Admin', path: "/admin" }, { icon: FiSettings, title: 'User Settings', path: "/settings" }, ]; @@ -16,9 +17,14 @@ interface SidebarItemsProps { } const SidebarItems: React.FC = ({ onClose }) => { + const textColor = useColorModeValue("ui.main", "#E2E8F0"); + const bgActive = useColorModeValue("#E2E8F0", "#4A5568"); const location = useLocation(); + const { user } = useUserStore(); + + const finalItems = user?.is_superuser ? [...items, { icon: FiUsers, title: 'Admin', path: "/admin" }] : items; - const listItems = items.map((item) => ( + const listItems = finalItems.map((item) => ( = ({ onClose }) => { p={2} key={item.title} style={location.pathname === item.path ? { - background: "#E2E8F0", + background: bgActive, borderRadius: "12px", } : {}} - color="ui.main" + color={textColor} onClick={onClose} > @@ -42,7 +48,7 @@ const SidebarItems: React.FC = ({ onClose }) => { {listItems} - + ); }; diff --git a/src/new-frontend/src/components/UserMenu.tsx b/src/new-frontend/src/components/UserMenu.tsx index 224af6012e..7f515fc3ed 100644 --- a/src/new-frontend/src/components/UserMenu.tsx +++ b/src/new-frontend/src/components/UserMenu.tsx @@ -32,7 +32,7 @@ const UserMenu: React.FC = () => { } as={Link} to="settings"> My profile - } onClick={handleLogout} color="ui.danger"> + } onClick={handleLogout} color="ui.danger" fontWeight="bold"> Log out diff --git a/src/new-frontend/src/modals/DeleteAlert.tsx b/src/new-frontend/src/modals/DeleteAlert.tsx index 665168cf9e..a86ee580a3 100644 --- a/src/new-frontend/src/modals/DeleteAlert.tsx +++ b/src/new-frontend/src/modals/DeleteAlert.tsx @@ -55,7 +55,7 @@ const Delete: React.FC = ({ type, id, isOpen, onClose }) => { > - + Delete {type} diff --git a/src/new-frontend/src/modals/DeleteConfirmation.tsx b/src/new-frontend/src/modals/DeleteConfirmation.tsx new file mode 100644 index 0000000000..2f9cf9ca00 --- /dev/null +++ b/src/new-frontend/src/modals/DeleteConfirmation.tsx @@ -0,0 +1,72 @@ +import React, { useState } from 'react'; + +import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button, useToast } from '@chakra-ui/react'; +import { useForm } from 'react-hook-form'; + +interface DeleteProps { + isOpen: boolean; + onClose: () => void; +} + +const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { + const toast = useToast(); + const cancelRef = React.useRef(null); + const [isLoading, setIsLoading] = useState(false); + const { handleSubmit } = useForm(); + + const onSubmit = async () => { + setIsLoading(true); + try { + // TODO: Delete user account when API is ready + onClose(); + } catch (err) { + toast({ + title: "An error occurred.", + description: `An error occurred while deleting your account.`, + status: "error", + isClosable: true, + }); + } finally { + setIsLoading(false); + } + } + + return ( + <> + + + + + Confirmation Required + + + + All your account data will be permanently deleted. If you're sure, please click 'Confirm' to proceed. + + + + + + + + + + + ) +} + +export default DeleteConfirmation; + + + + diff --git a/src/new-frontend/src/pages/Admin.tsx b/src/new-frontend/src/pages/Admin.tsx index 17b410fabb..ee97f863f5 100644 --- a/src/new-frontend/src/pages/Admin.tsx +++ b/src/new-frontend/src/pages/Admin.tsx @@ -42,7 +42,7 @@ const Admin: React.FC = () => { ) : ( users && - + User Management diff --git a/src/new-frontend/src/pages/Dashboard.tsx b/src/new-frontend/src/pages/Dashboard.tsx index e0a5e07a5b..c5b452c7df 100644 --- a/src/new-frontend/src/pages/Dashboard.tsx +++ b/src/new-frontend/src/pages/Dashboard.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Box, Text } from '@chakra-ui/react'; +import { Container, Text } from '@chakra-ui/react'; import { useUserStore } from '../store/user-store'; @@ -10,10 +10,10 @@ const Dashboard: React.FC = () => { return ( <> - + Hi, {user?.full_name || user?.email} 👋🏼 Welcome back, nice to see you again! - + ) diff --git a/src/new-frontend/src/pages/Items.tsx b/src/new-frontend/src/pages/Items.tsx index bcf1c04dd6..e3e273b0cd 100644 --- a/src/new-frontend/src/pages/Items.tsx +++ b/src/new-frontend/src/pages/Items.tsx @@ -44,7 +44,7 @@ const Items: React.FC = () => { ) : ( items && - + Items Management diff --git a/src/new-frontend/src/pages/Root.tsx b/src/new-frontend/src/pages/Layout.tsx similarity index 95% rename from src/new-frontend/src/pages/Root.tsx rename to src/new-frontend/src/pages/Layout.tsx index 3ebcd9ddcb..06471fb050 100644 --- a/src/new-frontend/src/pages/Root.tsx +++ b/src/new-frontend/src/pages/Layout.tsx @@ -7,7 +7,7 @@ import { Flex, useToast } from '@chakra-ui/react'; import { useUserStore } from '../store/user-store'; import UserMenu from '../components/UserMenu'; -const Root: React.FC = () => { +const Layout: React.FC = () => { const toast = useToast(); const { getUser } = useUserStore(); @@ -39,4 +39,4 @@ const Root: React.FC = () => { ); }; -export default Root; \ No newline at end of file +export default Layout; \ No newline at end of file diff --git a/src/new-frontend/src/pages/RecoverPassword.tsx b/src/new-frontend/src/pages/RecoverPassword.tsx index 4de1aba194..23c44124b3 100644 --- a/src/new-frontend/src/pages/RecoverPassword.tsx +++ b/src/new-frontend/src/pages/RecoverPassword.tsx @@ -42,7 +42,7 @@ const RecoverPassword: React.FC = () => { Password Recovery - + A password recovery email will be sent to the registered account. { return ( <> - + User Settings diff --git a/src/new-frontend/src/panels/Appearance.tsx b/src/new-frontend/src/panels/Appearance.tsx index 71caee6246..07f988069f 100644 --- a/src/new-frontend/src/panels/Appearance.tsx +++ b/src/new-frontend/src/panels/Appearance.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Button, Container, Heading, Radio, RadioGroup, Stack } from '@chakra-ui/react'; +import { Container, Heading, Radio, RadioGroup, Stack, useColorMode } from '@chakra-ui/react'; const Appearance: React.FC = () => { - const [value, setValue] = React.useState('system'); + const { colorMode, toggleColorMode } = useColorMode(); return ( <> @@ -11,21 +11,17 @@ const Appearance: React.FC = () => { Appearance - + - - Use system settings (default) - - Light + Light (default) Dark - - + ); } diff --git a/src/new-frontend/src/panels/ChangePassword.tsx b/src/new-frontend/src/panels/ChangePassword.tsx index 1f1f056a20..19bef94d66 100644 --- a/src/new-frontend/src/panels/ChangePassword.tsx +++ b/src/new-frontend/src/panels/ChangePassword.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import { Box, Button, Container, FormControl, FormLabel, Heading, Input } from '@chakra-ui/react'; +import { Box, Button, Container, FormControl, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; const ChangePassword: React.FC = () => { + const color = useColorModeValue("gray.700", "white"); return ( <> @@ -12,15 +13,15 @@ const ChangePassword: React.FC = () => { - Old password + Old password - New password + New password - Confirm new password + Confirm new password + ); diff --git a/src/new-frontend/src/panels/UserInformation.tsx b/src/new-frontend/src/panels/UserInformation.tsx index 13f6ba24fb..ff73912503 100644 --- a/src/new-frontend/src/panels/UserInformation.tsx +++ b/src/new-frontend/src/panels/UserInformation.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; -import { Button, Container, FormControl, FormLabel, Heading, Input, Text } from '@chakra-ui/react'; +import { Button, Container, FormControl, FormLabel, Heading, Input, Text, useColorModeValue } from '@chakra-ui/react'; import { useUserStore } from '../store/user-store'; const UserInformation: React.FC = () => { + const color = useColorModeValue("gray.700", "white"); const [editMode, setEditMode] = useState(false); const { user } = useUserStore(); @@ -20,21 +21,21 @@ const UserInformation: React.FC = () => { User Information - Full name + Full name { editMode ? - : - + : + {user?.full_name || "N/A"} } - Email + Email { editMode ? - : - + : + {user?.email || "N/A"} } From cb4e3bf5ef2cf023f60dfbc77877a0c6c7afc5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 17 Feb 2024 21:29:15 +0100 Subject: [PATCH 171/771] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20updat?= =?UTF-8?q?ing=20items=20and=20upgrade=20SQLModel=20to=200.0.16=20(which?= =?UTF-8?q?=20supports=20model=20object=20updates)=20(#601)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/app/app/api/api_v1/endpoints/items.py | 7 +++---- src/backend/app/app/models.py | 13 +++++++------ src/backend/app/pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/backend/app/app/api/api_v1/endpoints/items.py b/src/backend/app/app/api/api_v1/endpoints/items.py index e254cda368..9e186774c5 100644 --- a/src/backend/app/app/api/api_v1/endpoints/items.py +++ b/src/backend/app/app/api/api_v1/endpoints/items.py @@ -50,7 +50,7 @@ def create_item( """ Create new item. """ - item = Item.from_orm(item_in, update={"owner_id": current_user.id}) + item = Item.model_validate(item_in, update={"owner_id": current_user.id}) session.add(item) session.commit() session.refresh(item) @@ -69,9 +69,8 @@ def update_item( raise HTTPException(status_code=404, detail="Item not found") if not current_user.is_superuser and (item.owner_id != current_user.id): raise HTTPException(status_code=400, detail="Not enough permissions") - # TODO: check this actually works - update_dict = item_in.dict(exclude_unset=True) - item.from_orm(update_dict) + update_dict = item_in.model_dump(exclude_unset=True) + item.sqlmodel_update(update_dict) session.add(item) session.commit() session.refresh(item) diff --git a/src/backend/app/app/models.py b/src/backend/app/app/models.py index bb03308a70..505f8b9aa6 100644 --- a/src/backend/app/app/models.py +++ b/src/backend/app/app/models.py @@ -1,6 +1,6 @@ from typing import Union -from pydantic import BaseModel, EmailStr +from pydantic import EmailStr from sqlmodel import Field, Relationship, SQLModel @@ -29,11 +29,12 @@ class UserUpdate(UserBase): password: Union[str, None] = None -class UserUpdateMe(BaseModel): +class UserUpdateMe(SQLModel): password: Union[str, None] = None full_name: Union[str, None] = None email: Union[EmailStr, None] = None + # Database model, database table inferred from class name class User(UserBase, table=True): id: Union[int, None] = Field(default=None, primary_key=True) @@ -78,21 +79,21 @@ class ItemOut(ItemBase): # Generic message -class Message(BaseModel): +class Message(SQLModel): message: str # JSON payload containing access token -class Token(BaseModel): +class Token(SQLModel): access_token: str token_type: str = "bearer" # Contents of JWT token -class TokenPayload(BaseModel): +class TokenPayload(SQLModel): sub: Union[int, None] = None -class NewPassword(BaseModel): +class NewPassword(SQLModel): token: str new_password: str diff --git a/src/backend/app/pyproject.toml b/src/backend/app/pyproject.toml index 649ea0f1d4..a28e91b051 100644 --- a/src/backend/app/pyproject.toml +++ b/src/backend/app/pyproject.toml @@ -22,7 +22,7 @@ alembic = "^1.12.1" python-jose = {extras = ["cryptography"], version = "^3.3.0"} httpx = "^0.25.1" psycopg = {extras = ["binary"], version = "^3.1.13"} -sqlmodel = "^0.0.12" +sqlmodel = "^0.0.16" [tool.poetry.group.dev.dependencies] mypy = "^1.7.0" From 52e5c9baafe94b92685cd1acc84ca22a91d38834 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:55:19 -0500 Subject: [PATCH 172/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20update?= =?UTF-8?q?=20endpoints=20and=20regenerate=20client=20for=20new-frontend?= =?UTF-8?q?=20(#602)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/app/api/api_v1/endpoints/users.py | 72 +++++++++++++------ src/backend/app/app/models.py | 6 +- src/new-frontend/src/client/index.ts | 2 + .../src/client/models/UpdatePassword.ts | 9 +++ .../src/client/models/UserUpdateMe.ts | 1 - .../src/client/schemas/$UpdatePassword.ts | 16 +++++ .../src/client/schemas/$UserUpdateMe.ts | 3 - .../src/client/services/UsersService.ts | 57 ++++++++++----- 8 files changed, 121 insertions(+), 45 deletions(-) create mode 100644 src/new-frontend/src/client/models/UpdatePassword.ts create mode 100644 src/new-frontend/src/client/schemas/$UpdatePassword.ts diff --git a/src/backend/app/app/api/api_v1/endpoints/users.py b/src/backend/app/app/api/api_v1/endpoints/users.py index e3ec0fd34f..f3ddd354a1 100644 --- a/src/backend/app/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/app/api/api_v1/endpoints/users.py @@ -10,8 +10,10 @@ get_current_active_superuser, ) from app.core.config import settings +from app.core.security import get_password_hash, verify_password from app.models import ( Message, + UpdatePassword, User, UserCreate, UserCreateOpen, @@ -60,24 +62,40 @@ def create_user(*, session: SessionDep, user_in: UserCreate) -> Any: return user -@router.put("/me", response_model=UserOut) +@router.patch("/me", response_model=UserOut) def update_user_me( - *, session: SessionDep, body: UserUpdateMe, current_user: CurrentUser + *, session: SessionDep, user_in: UserUpdateMe, current_user: CurrentUser ) -> Any: """ Update own user. """ - # TODO: Refactor when SQLModel has update - # current_user_data = jsonable_encoder(current_user) - # user_in = UserUpdate(**current_user_data) - # if password is not None: - # user_in.password = password - # if full_name is not None: - # user_in.full_name = full_name - # if email is not None: - # user_in.email = email - # user = crud.user.update(session, session_obj=current_user, obj_in=user_in) - # return user + + user_data = user_in.model_dump(exclude_unset=True) + current_user.sqlmodel_update(user_data) + session.add(current_user) + session.commit() + session.refresh(current_user) + return current_user + + +@router.patch("/me/password", response_model=Message) +def update_password_me( + *, session: SessionDep, body: UpdatePassword, current_user: CurrentUser +) -> Any: + """ + Update own password. + """ + if not verify_password(body.current_password, current_user.hashed_password): + raise HTTPException(status_code=400, detail="Incorrect password") + if body.current_password == body.new_password: + raise HTTPException( + status_code=400, detail="New password cannot be the same as the current one" + ) + hashed_password = get_password_hash(body.new_password) + current_user.hashed_password = hashed_password + session.add(current_user) + session.commit() + return Message(message="Password updated successfully") @router.get("/me", response_model=UserOut) @@ -128,7 +146,7 @@ def read_user_by_id( return user -@router.put( +@router.patch( "/{user_id}", dependencies=[Depends(get_current_active_superuser)], response_model=UserOut, @@ -143,15 +161,23 @@ def update_user( Update a user. """ - # TODO: Refactor when SQLModel has update - # user = session.get(User, user_id) - # if not user: - # raise HTTPException( - # status_code=404, - # detail="The user with this username does not exist in the system", - # ) - # user = crud.user.update(session, db_obj=user, obj_in=user_in) - # return user + db_user = session.get(User, user_id) + if not db_user: + raise HTTPException( + status_code=404, + detail="The user with this username does not exist in the system", + ) + user_data = user_in.model_dump(exclude_unset=True) + extra_data = {} + if "password" in user_data: + password = user_data["password"] + hashed_password = get_password_hash(password) + extra_data["hashed_password"] = hashed_password + db_user.sqlmodel_update(user_data, update=extra_data) + session.add(db_user) + session.commit() + session.refresh(db_user) + return db_user @router.delete("/{user_id}") diff --git a/src/backend/app/app/models.py b/src/backend/app/app/models.py index 505f8b9aa6..9186b58702 100644 --- a/src/backend/app/app/models.py +++ b/src/backend/app/app/models.py @@ -30,11 +30,15 @@ class UserUpdate(UserBase): class UserUpdateMe(SQLModel): - password: Union[str, None] = None full_name: Union[str, None] = None email: Union[EmailStr, None] = None +class UpdatePassword(SQLModel): + current_password: str + new_password: str + + # Database model, database table inferred from class name class User(UserBase, table=True): id: Union[int, None] = Field(default=None, primary_key=True) diff --git a/src/new-frontend/src/client/index.ts b/src/new-frontend/src/client/index.ts index 59b24038bb..adc379de3e 100644 --- a/src/new-frontend/src/client/index.ts +++ b/src/new-frontend/src/client/index.ts @@ -15,6 +15,7 @@ export type { ItemUpdate } from './models/ItemUpdate'; export type { Message } from './models/Message'; export type { NewPassword } from './models/NewPassword'; export type { Token } from './models/Token'; +export type { UpdatePassword } from './models/UpdatePassword'; export type { UserCreate } from './models/UserCreate'; export type { UserCreateOpen } from './models/UserCreateOpen'; export type { UserOut } from './models/UserOut'; @@ -30,6 +31,7 @@ export { $ItemUpdate } from './schemas/$ItemUpdate'; export { $Message } from './schemas/$Message'; export { $NewPassword } from './schemas/$NewPassword'; export { $Token } from './schemas/$Token'; +export { $UpdatePassword } from './schemas/$UpdatePassword'; export { $UserCreate } from './schemas/$UserCreate'; export { $UserCreateOpen } from './schemas/$UserCreateOpen'; export { $UserOut } from './schemas/$UserOut'; diff --git a/src/new-frontend/src/client/models/UpdatePassword.ts b/src/new-frontend/src/client/models/UpdatePassword.ts new file mode 100644 index 0000000000..f0c4b69470 --- /dev/null +++ b/src/new-frontend/src/client/models/UpdatePassword.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type UpdatePassword = { + current_password: string; + new_password: string; +}; diff --git a/src/new-frontend/src/client/models/UserUpdateMe.ts b/src/new-frontend/src/client/models/UserUpdateMe.ts index bfed1a4f23..84ee306e6a 100644 --- a/src/new-frontend/src/client/models/UserUpdateMe.ts +++ b/src/new-frontend/src/client/models/UserUpdateMe.ts @@ -4,7 +4,6 @@ /* eslint-disable */ export type UserUpdateMe = { - password?: string; full_name?: string; email?: string; }; diff --git a/src/new-frontend/src/client/schemas/$UpdatePassword.ts b/src/new-frontend/src/client/schemas/$UpdatePassword.ts new file mode 100644 index 0000000000..18758819a0 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$UpdatePassword.ts @@ -0,0 +1,16 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $UpdatePassword = { + properties: { + current_password: { + type: 'string', + isRequired: true, +}, + new_password: { + type: 'string', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$UserUpdateMe.ts b/src/new-frontend/src/client/schemas/$UserUpdateMe.ts index d1408a1893..0aaf6abbef 100644 --- a/src/new-frontend/src/client/schemas/$UserUpdateMe.ts +++ b/src/new-frontend/src/client/schemas/$UserUpdateMe.ts @@ -4,9 +4,6 @@ /* eslint-disable */ export const $UserUpdateMe = { properties: { - password: { - type: 'string', -}, full_name: { type: 'string', }, diff --git a/src/new-frontend/src/client/services/UsersService.ts b/src/new-frontend/src/client/services/UsersService.ts index b5911d69f6..16481fc8b4 100644 --- a/src/new-frontend/src/client/services/UsersService.ts +++ b/src/new-frontend/src/client/services/UsersService.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ import type { Message } from '../models/Message'; +import type { UpdatePassword } from '../models/UpdatePassword'; import type { UserCreate } from '../models/UserCreate'; import type { UserCreateOpen } from '../models/UserCreateOpen'; import type { UserOut } from '../models/UserOut'; @@ -88,7 +89,7 @@ requestBody, requestBody: UserUpdateMe, }): CancelablePromise { return __request(OpenAPI, { - method: 'PUT', + method: 'PATCH', url: '/api/v1/users/me', body: requestBody, mediaType: 'application/json', @@ -98,6 +99,28 @@ requestBody: UserUpdateMe, }); } + /** + * Update Password Me + * Update own password. + * @returns Message Successful Response + * @throws ApiError + */ + public static updatePasswordMe({ +requestBody, +}: { +requestBody: UpdatePassword, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/v1/users/me/password', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** * Create User Open * Create new user without the need to be logged in. @@ -144,26 +167,22 @@ userId: number, } /** - * Update User - * Update a user. - * @returns UserOut Successful Response + * Delete User + * Delete a user. + * @returns Message Successful Response * @throws ApiError */ - public static updateUser({ + public static deleteUser({ userId, -requestBody, }: { userId: number, -requestBody: UserUpdate, -}): CancelablePromise { +}): CancelablePromise { return __request(OpenAPI, { - method: 'PUT', + method: 'DELETE', url: '/api/v1/users/{user_id}', path: { 'user_id': userId, }, - body: requestBody, - mediaType: 'application/json', errors: { 422: `Validation Error`, }, @@ -171,22 +190,26 @@ requestBody: UserUpdate, } /** - * Delete User - * Delete a user. - * @returns Message Successful Response + * Update User + * Update a user. + * @returns UserOut Successful Response * @throws ApiError */ - public static deleteUser({ + public static updateUser({ userId, +requestBody, }: { userId: number, -}): CancelablePromise { +requestBody: UserUpdate, +}): CancelablePromise { return __request(OpenAPI, { - method: 'DELETE', + method: 'PATCH', url: '/api/v1/users/{user_id}', path: { 'user_id': userId, }, + body: requestBody, + mediaType: 'application/json', errors: { 422: `Validation Error`, }, From c238ba81f8d5e91048ecfd3a50dcb5abb306bc23 Mon Sep 17 00:00:00 2001 From: Esteban Maya Date: Sun, 25 Feb 2024 10:04:47 -0500 Subject: [PATCH 173/771] =?UTF-8?q?=E2=99=BB=20Refactor=20items=20and=20se?= =?UTF-8?q?rvices=20endpoints=20to=20return=20count=20and=20data,=20and=20?= =?UTF-8?q?add=20CI=20tests=20(#599)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Esteban Maya Cadavid Co-authored-by: Sebastián Ramírez --- .github/workflows/test.yaml | 37 +++++++++++++++++++ .../app/app/api/api_v1/endpoints/items.py | 15 +++++--- .../app/app/api/api_v1/endpoints/users.py | 12 ++++-- src/backend/app/app/models.py | 11 ++++++ .../app/app/tests/api/api_v1/test_celery.py | 4 +- .../app/app/tests/api/api_v1/test_users.py | 5 ++- src/backend/app/pyproject.toml | 2 + src/docker-compose.override.yml | 28 ++++++++++---- src/docker-compose.yml | 20 +++++----- 9 files changed, 104 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000000..6cf1d1493d --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,37 @@ +name: Test + +on: + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + +jobs: + + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: src + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Docker Compose build + run: docker compose build + - name: Docker Compose remove old containers and volumes + run: docker compose down -v --remove-orphans + - name: Docker Compose up + run: docker compose up -d + - name: Docker Compose run tests + run: docker compose exec -T backend bash /app/tests-start.sh + - name: Docker Compose cleanup + run: docker compose down -v --remove-orphans diff --git a/src/backend/app/app/api/api_v1/endpoints/items.py b/src/backend/app/app/api/api_v1/endpoints/items.py index 9e186774c5..9878c09caf 100644 --- a/src/backend/app/app/api/api_v1/endpoints/items.py +++ b/src/backend/app/app/api/api_v1/endpoints/items.py @@ -1,15 +1,15 @@ from typing import Any from fastapi import APIRouter, HTTPException -from sqlmodel import select +from sqlmodel import select, func from app.api.deps import CurrentUser, SessionDep -from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message +from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message, ItemsOut router = APIRouter() -@router.get("/", response_model=list[ItemOut]) +@router.get("/", response_model=ItemsOut) def read_items( session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100 ) -> Any: @@ -17,9 +17,12 @@ def read_items( Retrieve items. """ + statment = select(func.count()).select_from(Item) + count = session.exec(statment).one() + if current_user.is_superuser: statement = select(Item).offset(skip).limit(limit) - return session.exec(statement).all() + items = session.exec(statement).all() else: statement = ( select(Item) @@ -27,7 +30,9 @@ def read_items( .offset(skip) .limit(limit) ) - return session.exec(statement).all() + items = session.exec(statement).all() + + return ItemsOut(data=items, count=count) @router.get("/{id}", response_model=ItemOut) diff --git a/src/backend/app/app/api/api_v1/endpoints/users.py b/src/backend/app/app/api/api_v1/endpoints/users.py index f3ddd354a1..0e71f1f854 100644 --- a/src/backend/app/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/app/api/api_v1/endpoints/users.py @@ -1,7 +1,7 @@ from typing import Any, List from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import select +from sqlmodel import select, func from app import crud from app.api.deps import ( @@ -18,6 +18,7 @@ UserCreate, UserCreateOpen, UserOut, + UsersOut, UserUpdate, UserUpdateMe, ) @@ -29,15 +30,20 @@ @router.get( "/", dependencies=[Depends(get_current_active_superuser)], - response_model=List[UserOut], + response_model=UsersOut ) def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: """ Retrieve users. """ + + statment = select(func.count()).select_from(User) + count = session.exec(statment).one() + statement = select(User).offset(skip).limit(limit) users = session.exec(statement).all() - return users + + return UsersOut(data=users, count=count) @router.post( diff --git a/src/backend/app/app/models.py b/src/backend/app/app/models.py index 9186b58702..5331a96b1f 100644 --- a/src/backend/app/app/models.py +++ b/src/backend/app/app/models.py @@ -51,6 +51,11 @@ class UserOut(UserBase): id: int +class UsersOut(SQLModel): + data: list[UserOut] + count: int + + # Shared properties class ItemBase(SQLModel): title: str @@ -80,6 +85,12 @@ class Item(ItemBase, table=True): # Properties to return via API, id is always required class ItemOut(ItemBase): id: int + owner_id: int + + +class ItemsOut(SQLModel): + data: list[ItemOut] + count: int # Generic message diff --git a/src/backend/app/app/tests/api/api_v1/test_celery.py b/src/backend/app/app/tests/api/api_v1/test_celery.py index 7b10a331f2..80973064ff 100644 --- a/src/backend/app/app/tests/api/api_v1/test_celery.py +++ b/src/backend/app/app/tests/api/api_v1/test_celery.py @@ -8,11 +8,11 @@ def test_celery_worker_test( client: TestClient, superuser_token_headers: Dict[str, str] ) -> None: - data = {"msg": "test"} + data = {"message": "test"} r = client.post( f"{settings.API_V1_STR}/utils/test-celery/", json=data, headers=superuser_token_headers, ) response = r.json() - assert response["msg"] == "Word received" + assert response["message"] == "Word received" diff --git a/src/backend/app/app/tests/api/api_v1/test_users.py b/src/backend/app/app/tests/api/api_v1/test_users.py index ba22bfe969..44d97f6649 100644 --- a/src/backend/app/app/tests/api/api_v1/test_users.py +++ b/src/backend/app/app/tests/api/api_v1/test_users.py @@ -110,6 +110,7 @@ def test_retrieve_users( r = client.get(f"{settings.API_V1_STR}/users/", headers=superuser_token_headers) all_users = r.json() - assert len(all_users) > 1 - for item in all_users: + assert len(all_users["data"]) > 1 + assert "count" in all_users + for item in all_users["data"]: assert "email" in item diff --git a/src/backend/app/pyproject.toml b/src/backend/app/pyproject.toml index a28e91b051..e15178463d 100644 --- a/src/backend/app/pyproject.toml +++ b/src/backend/app/pyproject.toml @@ -23,6 +23,8 @@ python-jose = {extras = ["cryptography"], version = "^3.3.0"} httpx = "^0.25.1" psycopg = {extras = ["binary"], version = "^3.1.13"} sqlmodel = "^0.0.16" +# Pin bcrypt until passlib supports the latest +bcrypt = "4.0.1" [tool.poetry.group.dev.dependencies] mypy = "^1.7.0" diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 469caed772..256a684d26 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -28,10 +28,22 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-traefik-public-http.rule=Host(`${DOMAIN?Variable not set}`) - traefik.http.services.${STACK_NAME?Variable not set}-traefik-public.loadbalancer.server.port=80 + db: + ports: + - "5432:5432" + pgadmin: ports: - "5050:5050" + # Uncomment the section below to be able to debug locally + # queue: + # ports: + # - "5671:5671" + # - "5672:5672" + # - "15672:15672" + # - "15671:15671" + flower: ports: - "5555:5555" @@ -84,14 +96,14 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`old-frontend.localhost.tiangolo.com`) - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - new-frontend: - build: - context: ./new-frontend - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) - - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 +# new-frontend: +# build: +# context: ./new-frontend +# labels: +# - traefik.enable=true +# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} +# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) +# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 networks: traefik-public: diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 7c8d1f4e26..39cccb0fb8 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -191,16 +191,16 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - new-frontend: - image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' - build: - context: ./new-frontend - deploy: - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) - - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 +# new-frontend: +# image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' +# build: +# context: ./new-frontend +# deploy: +# labels: +# - traefik.enable=true +# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} +# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) +# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 volumes: From 763fb43377fbc0a49160550dccc304575b790df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Feb 2024 17:28:26 +0100 Subject: [PATCH 174/771] =?UTF-8?q?=E2=99=BB=20Re-structure=20Docker=20Com?= =?UTF-8?q?pose=20files,=20discard=20Docker=20Swarm=20specific=20logic=20(?= =?UTF-8?q?#607)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docker-compose.override.yml | 36 ++----- src/docker-compose.yml | 172 +++++++++++++++----------------- 2 files changed, 86 insertions(+), 122 deletions(-) diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 256a684d26..6e12f41305 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -5,6 +5,7 @@ services: ports: - "80:80" - "8090:8080" + # Duplicate the command from docker-compose.yml to add --api.insecure=true command: # Enable Docker in Traefik, so that it reads labels from Docker services - --providers.docker @@ -13,8 +14,6 @@ services: - --providers.docker.constraints=Label(`traefik.constraint-label-stack`, `${TRAEFIK_TAG?Variable not set}`) # Do not expose all Docker services, only the ones explicitly exposed - --providers.docker.exposedbydefault=false - # Disable Docker Swarm mode for local development - # - --providers.docker.swarmmode # Enable the access log, with HTTP requests - --accesslog # Enable the Traefik log, for configurations and errors @@ -24,7 +23,6 @@ services: # Enable the Dashboard and API in insecure mode for local development - --api.insecure=true labels: - - traefik.enable=true - traefik.http.routers.${STACK_NAME?Variable not set}-traefik-public-http.rule=Host(`${DOMAIN?Variable not set}`) - traefik.http.services.${STACK_NAME?Variable not set}-traefik-public.loadbalancer.server.port=80 @@ -36,13 +34,12 @@ services: ports: - "5050:5050" - # Uncomment the section below to be able to debug locally - # queue: - # ports: - # - "5671:5671" - # - "5672:5672" - # - "15672:15672" - # - "15671:15671" + queue: + ports: + - "5671:5671" + - "5672:5672" + - "15672:15672" + - "15671:15671" flower: ports: @@ -62,13 +59,8 @@ services: args: INSTALL_DEV: ${INSTALL_DEV-true} INSTALL_JUPYTER: ${INSTALL_JUPYTER-true} - # command: bash -c "while true; do sleep 1; done" # Infinite loop to keep container live doing nothing + # command: sleep infinity # Infinite loop to keep container alive doing nothing command: /start-reload.sh - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`) - - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 celeryworker: volumes: @@ -90,20 +82,8 @@ services: args: FRONTEND_ENV: dev labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} # - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`old-frontend.localhost.tiangolo.com`) - - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - -# new-frontend: -# build: -# context: ./new-frontend -# labels: -# - traefik.enable=true -# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} -# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) -# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 networks: traefik-public: diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 39cccb0fb8..dca06388f9 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -16,57 +16,51 @@ services: - --providers.docker.constraints=Label(`traefik.constraint-label-stack`, `${TRAEFIK_TAG?Variable not set}`) # Do not expose all Docker services, only the ones explicitly exposed - --providers.docker.exposedbydefault=false - # Enable Docker Swarm mode - - --providers.docker.swarmmode # Enable the access log, with HTTP requests - --accesslog # Enable the Traefik log, for configurations and errors - --log # Enable the Dashboard and API - --api - deploy: - placement: - constraints: - - node.role == manager - labels: - # Enable Traefik for this service, to make it available in the public network - - traefik.enable=true - # Use the traefik-public network (declared below) - - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} - # Use the custom label "traefik.constraint-label=traefik-public" - # This public Traefik will only use services with this label - - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} - # traefik-http set up only to use the middleware to redirect to https - - traefik.http.middlewares.${STACK_NAME?Variable not set}-https-redirect.redirectscheme.scheme=https - - traefik.http.middlewares.${STACK_NAME?Variable not set}-https-redirect.redirectscheme.permanent=true - # Handle host with and without "www" to redirect to only one of them - # Uses environment variable DOMAIN - # To disable www redirection remove the Host() you want to discard, here and - # below for HTTPS - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.entrypoints=http - # traefik-https the actual router using HTTPS - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.entrypoints=https - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.tls=true - # Use the "le" (Let's Encrypt) resolver created below - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.tls.certresolver=le - # Define the port inside of the Docker service to use - - traefik.http.services.${STACK_NAME?Variable not set}-proxy.loadbalancer.server.port=80 - # Handle domain with and without "www" to redirect to only one - # To disable www redirection remove the next line - - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.regex=^https?://(www.)?(${DOMAIN?Variable not set})/(.*) - # Redirect a domain with www to non-www - # To disable it remove the next line - - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.replacement=https://${DOMAIN?Variable not set}/$${3} - # Redirect a domain without www to www - # To enable it remove the previous line and uncomment the next - # - traefik.http.middlewares.${STACK_NAME}-www-redirect.redirectregex.replacement=https://www.${DOMAIN}/$${3} - # Middleware to redirect www, to disable it remove the next line - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.middlewares=${STACK_NAME?Variable not set}-www-redirect - # Middleware to redirect www, and redirect HTTP to HTTPS - # to disable www redirection remove the section: ${STACK_NAME?Variable not set}-www-redirect, - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.middlewares=${STACK_NAME?Variable not set}-www-redirect,${STACK_NAME?Variable not set}-https-redirect + labels: + # Enable Traefik for this service, to make it available in the public network + - traefik.enable=true + # Use the traefik-public network (declared below) + - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} + # Use the custom label "traefik.constraint-label=traefik-public" + # This public Traefik will only use services with this label + - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} + # traefik-http set up only to use the middleware to redirect to https + - traefik.http.middlewares.${STACK_NAME?Variable not set}-https-redirect.redirectscheme.scheme=https + - traefik.http.middlewares.${STACK_NAME?Variable not set}-https-redirect.redirectscheme.permanent=true + # Handle host with and without "www" to redirect to only one of them + # Uses environment variable DOMAIN + # To disable www redirection remove the Host() you want to discard, here and + # below for HTTPS + - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.entrypoints=http + # traefik-https the actual router using HTTPS + - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.tls=true + # Use the "le" (Let's Encrypt) resolver created below + - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.tls.certresolver=le + # Define the port inside of the Docker service to use + - traefik.http.services.${STACK_NAME?Variable not set}-proxy.loadbalancer.server.port=80 + # Handle domain with and without "www" to redirect to only one + # To disable www redirection remove the next line + - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.regex=^https?://(www.)?(${DOMAIN?Variable not set})/(.*) + # Redirect a domain with www to non-www + # To disable it remove the next line + - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.replacement=https://${DOMAIN?Variable not set}/$${3} + # Redirect a domain without www to www + # To enable it remove the previous line and uncomment the next + # - traefik.http.middlewares.${STACK_NAME}-www-redirect.redirectregex.replacement=https://www.${DOMAIN}/$${3} + # Middleware to redirect www, to disable it remove the next line + - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.middlewares=${STACK_NAME?Variable not set}-www-redirect + # Middleware to redirect www, and redirect HTTP to HTTPS + # to disable www redirection remove the section: ${STACK_NAME?Variable not set}-www-redirect, + - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.middlewares=${STACK_NAME?Variable not set}-www-redirect,${STACK_NAME?Variable not set}-https-redirect db: image: postgres:12 @@ -76,10 +70,6 @@ services: - .env environment: - PGDATA=/var/lib/postgresql/data/pgdata - deploy: - placement: - constraints: - - node.labels.${STACK_NAME?Variable not set}.app-db-data == true pgadmin: image: dpage/pgadmin4 @@ -90,19 +80,18 @@ services: - db env_file: - .env - deploy: - labels: - - traefik.enable=true - - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} - - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.rule=Host(`pgadmin.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.entrypoints=http - - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.middlewares=${STACK_NAME?Variable not set}-https-redirect - - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.rule=Host(`pgadmin.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.entrypoints=https - - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.tls=true - - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.tls.certresolver=le - - traefik.http.services.${STACK_NAME?Variable not set}-pgadmin.loadbalancer.server.port=5050 + labels: + - traefik.enable=true + - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} + - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} + - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.rule=Host(`pgadmin.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.entrypoints=http + - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.middlewares=${STACK_NAME?Variable not set}-https-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.rule=Host(`pgadmin.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.tls.certresolver=le + - traefik.http.services.${STACK_NAME?Variable not set}-pgadmin.loadbalancer.server.port=5050 queue: image: rabbitmq:3 @@ -123,19 +112,18 @@ services: # For the "Broker" tab to work in the flower UI, uncomment the following command argument, # and change the queue service's image as well # - "--broker_api=http://guest:guest@queue:15672/api//" - deploy: - labels: - - traefik.enable=true - - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} - - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.rule=Host(`flower.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.entrypoints=http - - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.middlewares=${STACK_NAME?Variable not set}-https-redirect - - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.rule=Host(`flower.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.entrypoints=https - - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls=true - - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls.certresolver=le - - traefik.http.services.${STACK_NAME?Variable not set}-flower.loadbalancer.server.port=5555 + labels: + - traefik.enable=true + - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} + - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} + - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.rule=Host(`flower.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.entrypoints=http + - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.middlewares=${STACK_NAME?Variable not set}-https-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.rule=Host(`flower.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls.certresolver=le + - traefik.http.services.${STACK_NAME?Variable not set}-flower.loadbalancer.server.port=5555 backend: image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' @@ -153,12 +141,11 @@ services: dockerfile: backend.dockerfile args: INSTALL_DEV: ${INSTALL_DEV-false} - deploy: - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`) - - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 + labels: + - traefik.enable=true + - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`) + - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 celeryworker: image: '${DOCKER_IMAGE_CELERYWORKER?Variable not set}:${TAG-latest}' @@ -184,24 +171,21 @@ services: context: ./frontend args: FRONTEND_ENV: ${FRONTEND_ENV-production} - deploy: - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 + labels: + - traefik.enable=true + - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) + - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 # new-frontend: # image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' # build: # context: ./new-frontend -# deploy: -# labels: -# - traefik.enable=true -# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} -# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) -# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 - + # labels: + # - traefik.enable=true + # - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} + # - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) + # - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 volumes: app-db-data: From 85e95ce00bcbbf4a688433ed927a85350a7fd3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Feb 2024 17:50:12 +0100 Subject: [PATCH 175/771] =?UTF-8?q?=F0=9F=94=A5=20Clean=20up=20old=20files?= =?UTF-8?q?=20no=20longer=20relevant=20(#608)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/{test.yaml => test.yml} | 2 +- .gitignore | 2 - .travis.yml | 12 ---- CONTRIBUTING.md | 83 ----------------------- README.md | 4 -- scripts/dev-fsfp-back.sh | 22 ------ scripts/dev-fsfp.sh | 13 ---- scripts/dev-link.sh | 34 ---------- scripts/discard-dev-files.sh | 13 ---- scripts/generate_cookiecutter_config.py | 20 ------ scripts/test.sh | 16 ----- src/.env | 3 +- src/.gitignore | 3 - 13 files changed, 2 insertions(+), 225 deletions(-) rename .github/workflows/{test.yaml => test.yml} (95%) delete mode 100644 .travis.yml delete mode 100644 CONTRIBUTING.md delete mode 100644 scripts/dev-fsfp-back.sh delete mode 100644 scripts/dev-fsfp.sh delete mode 100644 scripts/dev-link.sh delete mode 100644 scripts/discard-dev-files.sh delete mode 100644 scripts/generate_cookiecutter_config.py delete mode 100644 scripts/test.sh delete mode 100755 src/.gitignore diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yml similarity index 95% rename from .github/workflows/test.yaml rename to .github/workflows/test.yml index 6cf1d1493d..aabe1888d5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: with: python-version: '3.10' - - name: Docker Compose build + - name: Run tests run: docker compose build - name: Docker Compose remove old containers and volumes run: docker compose down -v --remove-orphans diff --git a/.gitignore b/.gitignore index 5d778b370c..be16fd8a57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ .vscode -testing-project .mypy_cache poetry.lock -dev-link/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ad7e0349a3..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -sudo: required - -language: python - -install: - - pip install cookiecutter - -services: - - docker - -script: -- bash ./scripts/test.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index d95d76171c..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,83 +0,0 @@ -# Contributing - -Here are some short guidelines to guide you if you want to contribute to the development of the Full Stack FastAPI PostgreSQL project generator itself. - -After you clone the project, there are several scripts that can help during development. - -* `./scripts/dev-fsfp.sh`: - -Generate a new default project `dev-fsfp`. - -Call it from one level above the project directory. So, if the project is at `~/code/full-stack-fastapi-postgresql/`, call it from `~/code/`, like: - -```console -$ cd ~/code/ - -$ bash ./full-stack-fastapi-postgresql/scripts/dev-fsfp.sh -``` - -It will generate a new project with all the defaults at `~/code/dev-fsfp/`. - -You can go to that directory with a full new project, edit files and test things, for example: - -```console -$ cd ./dev-fsfp/ - -$ docker-compose up -d -``` - -It is outside of the project generator directory to let you add Git to it and compare versions and changes. - -* `./scripts/dev-fsfp-back.sh`: - -Move the changes from a project `dev-fsfp` back to the project generator. - -You would call it after calling `./scripts/dev-fsfp.sh` and adding some modifications to `dev-fsfp`. - -Call it from one level above the project directory. So, if the project is at `~/code/full-stack-fastapi-postgresql/`, call it from `~/code/`, like: - -```console -$ cd ~/code/ - -$ bash ./full-stack-fastapi-postgresql/scripts/dev-fsfp-back.sh -``` - -That will also contain all the generated files with the generated variables, but it will let you compare the changes in `dev-fsfp` and the source in the project generator with git, and see what to commit. - -* `./scripts/discard-dev-files.sh`: - -After using `./scripts/dev-fsfp-back.sh`, there will be a bunch of generated files with the variables for the generated project that you don't want to commit, like `README.md` and `.gitlab-ci.yml`. - -To discard all those changes at once, run `discard-dev-files.sh` from the root of the project, e.g.: - -```console -$ cd ~/code/full-stack-fastapi-postgresql/ - -$ bash ./scripts/dev-fsfp-back.sh -``` - -* `./scripts/test.sh`: - -Run the tests. It creates a project `testing-project` *inside* of the project generator and runs its tests. - -Call it from the root of the project, e.g.: - -```console -$ cd ~/code/full-stack-fastapi-postgresql/ - -$ bash ./scripts/test.sh -``` - -* `./scripts/dev-link.sh`: - -Set up a local directory with links to the files for live development with the source files. - -This script generates a project `dev-link` *inside* the project generator, just to generate the `.env` and `./frontend/.env` files. - -Then it removes everything except those 2 files. - -Then it creates links for each of the source files, and adds those 2 files back. - -The end result is that you can go into the `dev-link` directory and develop locally with it as if it was a generated project, with all the variables set. But all the changes are actually done directly in the source files. - -This is probably a lot faster to iterate than using `./scripts/dev-fsfp.sh`. But it's tested only in Linux, it might not work in other systems. diff --git a/README.md b/README.md index 87d367d130..815e9b8d13 100644 --- a/README.md +++ b/README.md @@ -112,10 +112,6 @@ The input variables, with their default values (some auto generated) are: * `project_slug`: The development friendly name of the project. By default, based on the project name * `domain_main`: The domain in where to deploy the project for production (from the branch `production`), used by the load balancer, backend, etc. By default, based on the project slug. * `domain_staging`: The domain in where to deploy while staging (before production) (from the branch `master`). By default, based on the main domain. - -* `docker_swarm_stack_name_main`: The name of the stack while deploying to Docker in Swarm mode for production. By default, based on the domain. -* `docker_swarm_stack_name_staging`: The name of the stack while deploying to Docker in Swarm mode for staging. By default, based on the domain. - * `secret_key`: Backend server secret key. Use the method above to generate it. * `first_superuser`: The first superuser generated, with it you will be able to create more users, etc. By default, based on the domain. * `first_superuser_password`: First superuser password. Use the method above to generate it. diff --git a/scripts/dev-fsfp-back.sh b/scripts/dev-fsfp-back.sh deleted file mode 100644 index 95e9b78102..0000000000 --- a/scripts/dev-fsfp-back.sh +++ /dev/null @@ -1,22 +0,0 @@ -#! /usr/bin/env bash - -# Run this script from outside the project, to integrate a dev-fsfp project with changes and review modifications - -# Exit in case of error -set -e - -if [ ! -d ./full-stack-fastapi-postgresql ] ; then - echo "Run this script from outside the project, to integrate a sibling dev-fsfp project with changes and review modifications" - exit 1 -fi - -if [ $(uname -s) = "Linux" ]; then - echo "Remove __pycache__ files" - sudo find ./dev-fsfp/ -type d -name __pycache__ -exec rm -r {} \+ -fi - -rm -rf ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/* - -rsync -a --exclude=node_modules ./dev-fsfp/* ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/ - -rsync -a ./dev-fsfp/{.env,.gitignore,.gitlab-ci.yml} ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/ diff --git a/scripts/dev-fsfp.sh b/scripts/dev-fsfp.sh deleted file mode 100644 index 9afbe30b15..0000000000 --- a/scripts/dev-fsfp.sh +++ /dev/null @@ -1,13 +0,0 @@ -#! /usr/bin/env bash - -# Exit in case of error -set -e - -if [ ! -d ./full-stack-fastapi-postgresql ] ; then - echo "Run this script from outside the project, to generate a sibling dev-fsfp project with independent git" - exit 1 -fi - -rm -rf ./dev-fsfp - -cookiecutter --no-input -f ./full-stack-fastapi-postgresql project_name="Dev FSFP" diff --git a/scripts/dev-link.sh b/scripts/dev-link.sh deleted file mode 100644 index 3b59f9d52a..0000000000 --- a/scripts/dev-link.sh +++ /dev/null @@ -1,34 +0,0 @@ -#! /usr/bin/env bash - -# Exit in case of error -set -e - -# Run this from the root of the project to generate a dev-link project -# It will contain a link to each of the files of the generator, except for -# .env and frontend/.env, that will be the generated ones -# This allows developing with a live stack while keeping the same source code -# Without having to generate dev-fsfp and integrating back all the files - -rm -rf dev-link -mkdir -p tmp-dev-link/frontend - -cookiecutter --no-input -f ./ project_name="Dev Link" - -mv ./dev-link/.env ./tmp-dev-link/ -mv ./dev-link/frontend/.env ./tmp-dev-link/frontend/ - -rm -rf ./dev-link/ -mkdir -p ./dev-link/ - -cd ./dev-link/ - -for f in ../\{\{cookiecutter.project_slug\}\}/* ; do - ln -s "$f" ./ -done - -cd .. - -mv ./tmp-dev-link/.env ./dev-link/ -mv ./tmp-dev-link/frontend/.env ./dev-link/frontend/ - -rm -rf ./tmp-dev-link diff --git a/scripts/discard-dev-files.sh b/scripts/discard-dev-files.sh deleted file mode 100644 index 7a07a70bb3..0000000000 --- a/scripts/discard-dev-files.sh +++ /dev/null @@ -1,13 +0,0 @@ -#! /usr/bin/env bash - -set -e - -rm -rf \{\{cookiecutter.project_slug\}\}/.git -rm -rf \{\{cookiecutter.project_slug\}\}/backend/app/poetry.lock -rm -rf \{\{cookiecutter.project_slug\}\}/frontend/node_modules -rm -rf \{\{cookiecutter.project_slug\}\}/frontend/dist -git checkout \{\{cookiecutter.project_slug\}\}/README.md -git checkout \{\{cookiecutter.project_slug\}\}/.gitlab-ci.yml -git checkout \{\{cookiecutter.project_slug\}\}/cookiecutter-config-file.yml -git checkout \{\{cookiecutter.project_slug\}\}/.env -git checkout \{\{cookiecutter.project_slug\}\}/frontend/.env diff --git a/scripts/generate_cookiecutter_config.py b/scripts/generate_cookiecutter_config.py deleted file mode 100644 index 12a9a6ed4e..0000000000 --- a/scripts/generate_cookiecutter_config.py +++ /dev/null @@ -1,20 +0,0 @@ -import json -from collections import OrderedDict -import oyaml as yaml -from pathlib import Path -cookie_path = Path('./cookiecutter.json') -out_path = Path('./{{cookiecutter.project_slug}}/cookiecutter-config-file.yml') - -with open(cookie_path) as f: - cookie_config = json.load(f) -config_out = OrderedDict() - -for key, value in cookie_config.items(): - if key.startswith('_'): - config_out[key] = value - else: - config_out[key] = '{{ cookiecutter.' + key + ' }}' -config_out['_template'] = './' - -with open(out_path, 'w') as out_f: - out_f.write(yaml.dump({'default_context': config_out}, line_break=None, width=200)) diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100644 index 36cc535fa8..0000000000 --- a/scripts/test.sh +++ /dev/null @@ -1,16 +0,0 @@ -#! /usr/bin/env bash - -# Exit in case of error -set -e - -# Run this from the root of the project - -rm -rf ./testing-project - -cookiecutter --no-input -f ./ project_name="Testing Project" - -cd ./testing-project - -bash ./scripts/test.sh "$@" - -cd ../ diff --git a/src/.env b/src/.env index 9ffb6a7076..78bf4c65cd 100644 --- a/src/.env +++ b/src/.env @@ -1,6 +1,5 @@ # Update this with your app domain DOMAIN=localhost -# DOMAIN=local.dockertoolbox.tiangolo.com # DOMAIN=localhost.tiangolo.com STACK_NAME=full-stack-fastapi-postgresql @@ -17,7 +16,7 @@ DOCKER_IMAGE_NEW_FRONTEND=new-frontend # Backend BACKEND_CORS_ORIGINS="[\"http://localhost\", \"http://localhost:4200\", \"http://localhost:3000\", \"http://localhost:8080\", \"https://localhost\", \"https://localhost:4200\", \"https://localhost:3000\", \"https://localhost:8080\", \"http://local.dockertoolbox.tiangolo.com\", \"http://localhost.tiangolo.com\"]" -PROJECT_NAME="Full Stack FastAPI PostgreSQL" +PROJECT_NAME="FastAPI Project" SECRET_KEY=changethis FIRST_SUPERUSER=admin@example.com FIRST_SUPERUSER_PASSWORD=changethis diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100755 index 1230031ec4..0000000000 --- a/src/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.vscode -.mypy_cache -docker-stack.yml From 470b505422d08ef6eb8d57fbf0695247386a3b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Feb 2024 18:48:02 +0100 Subject: [PATCH 176/771] =?UTF-8?q?=F0=9F=9A=9A=20Refactor=20and=20simplif?= =?UTF-8?q?y=20backend=20file=20structure=20(#609)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/.dockerignore | 8 ++++++++ src/backend/{app => }/.flake8 | 0 src/backend/.gitignore | 3 +++ src/backend/{app => }/alembic.ini | 2 +- src/backend/app/.gitignore | 3 --- src/backend/app/{app => }/__init__.py | 0 src/backend/app/{app => }/api/__init__.py | 0 .../app/{app => }/api/api_v1/__init__.py | 0 src/backend/app/{app => }/api/api_v1/api.py | 0 .../{app => }/api/api_v1/endpoints/__init__.py | 0 .../app/{app => }/api/api_v1/endpoints/items.py | 0 .../app/{app => }/api/api_v1/endpoints/login.py | 0 .../app/{app => }/api/api_v1/endpoints/users.py | 0 .../app/{app => }/api/api_v1/endpoints/utils.py | 0 src/backend/app/{app => }/api/deps.py | 0 src/backend/app/{app => }/backend_pre_start.py | 0 .../app/{app => }/celeryworker_pre_start.py | 0 src/backend/app/{app => }/core/__init__.py | 0 src/backend/app/{app => }/core/celery_app.py | 0 src/backend/app/{app => }/core/config.py | 0 src/backend/app/{app => }/core/security.py | 0 src/backend/app/{app => }/crud/__init__.py | 0 src/backend/app/{app => }/crud/base.py | 0 src/backend/app/{app => }/crud/crud_item.py | 0 src/backend/app/{app => }/crud/crud_user.py | 0 src/backend/app/{app => }/db/__init__.py | 0 src/backend/app/{app => }/db/engine.py | 0 src/backend/app/{app => }/db/init_db.py | 0 .../email-templates/build/new_account.html | 0 .../email-templates/build/reset_password.html | 0 .../email-templates/build/test_email.html | 0 .../email-templates/src/new_account.mjml | 0 .../email-templates/src/reset_password.mjml | 0 .../email-templates/src/test_email.mjml | 0 src/backend/app/{app => }/initial_data.py | 0 src/backend/app/{app => }/main.py | 0 src/backend/app/{app => }/models.py | 0 src/backend/app/{app => }/schemas/__init__.py | 0 src/backend/app/{app => }/schemas/item.py | 0 src/backend/app/{app => }/schemas/msg.py | 0 src/backend/app/{app => }/schemas/token.py | 0 src/backend/app/{app => }/schemas/user.py | 0 src/backend/app/{app => }/tests/.gitignore | 0 src/backend/app/{app => }/tests/__init__.py | 0 src/backend/app/{app => }/tests/api/__init__.py | 0 .../app/{app => }/tests/api/api_v1/__init__.py | 0 .../{app => }/tests/api/api_v1/test_celery.py | 0 .../{app => }/tests/api/api_v1/test_items.py | 0 .../{app => }/tests/api/api_v1/test_login.py | 0 .../{app => }/tests/api/api_v1/test_users.py | 0 src/backend/app/{app => }/tests/conftest.py | 0 .../app/{app => }/tests/crud/__init__.py | 0 .../app/{app => }/tests/crud/test_item.py | 0 .../app/{app => }/tests/crud/test_user.py | 0 .../app/{app => }/tests/utils/__init__.py | 0 src/backend/app/{app => }/tests/utils/item.py | 0 src/backend/app/{app => }/tests/utils/user.py | 0 src/backend/app/{app => }/tests/utils/utils.py | 0 src/backend/app/{app => }/tests_pre_start.py | 0 src/backend/app/{app => }/utils.py | 0 src/backend/app/{app => }/worker.py | 0 src/backend/backend.dockerfile | 17 +++++++++-------- src/backend/celeryworker.dockerfile | 17 ++++++----------- src/backend/{app => }/mypy.ini | 0 src/backend/{app => }/prestart.sh | 0 src/backend/{app => }/pyproject.toml | 0 src/backend/{app => }/scripts/format-imports.sh | 0 src/backend/{app => }/scripts/format.sh | 0 src/backend/{app => }/scripts/lint.sh | 0 src/backend/{app => }/scripts/test-cov-html.sh | 0 src/backend/{app => }/scripts/test.sh | 0 src/backend/{app => }/tests-start.sh | 0 src/backend/{app => }/worker-start.sh | 0 src/docker-compose.override.yml | 8 ++------ 74 files changed, 29 insertions(+), 29 deletions(-) create mode 100644 src/backend/.dockerignore rename src/backend/{app => }/.flake8 (100%) rename src/backend/{app => }/alembic.ini (98%) delete mode 100644 src/backend/app/.gitignore rename src/backend/app/{app => }/__init__.py (100%) rename src/backend/app/{app => }/api/__init__.py (100%) rename src/backend/app/{app => }/api/api_v1/__init__.py (100%) rename src/backend/app/{app => }/api/api_v1/api.py (100%) rename src/backend/app/{app => }/api/api_v1/endpoints/__init__.py (100%) rename src/backend/app/{app => }/api/api_v1/endpoints/items.py (100%) rename src/backend/app/{app => }/api/api_v1/endpoints/login.py (100%) rename src/backend/app/{app => }/api/api_v1/endpoints/users.py (100%) rename src/backend/app/{app => }/api/api_v1/endpoints/utils.py (100%) rename src/backend/app/{app => }/api/deps.py (100%) rename src/backend/app/{app => }/backend_pre_start.py (100%) rename src/backend/app/{app => }/celeryworker_pre_start.py (100%) rename src/backend/app/{app => }/core/__init__.py (100%) rename src/backend/app/{app => }/core/celery_app.py (100%) rename src/backend/app/{app => }/core/config.py (100%) rename src/backend/app/{app => }/core/security.py (100%) rename src/backend/app/{app => }/crud/__init__.py (100%) rename src/backend/app/{app => }/crud/base.py (100%) rename src/backend/app/{app => }/crud/crud_item.py (100%) rename src/backend/app/{app => }/crud/crud_user.py (100%) rename src/backend/app/{app => }/db/__init__.py (100%) rename src/backend/app/{app => }/db/engine.py (100%) rename src/backend/app/{app => }/db/init_db.py (100%) rename src/backend/app/{app => }/email-templates/build/new_account.html (100%) rename src/backend/app/{app => }/email-templates/build/reset_password.html (100%) rename src/backend/app/{app => }/email-templates/build/test_email.html (100%) rename src/backend/app/{app => }/email-templates/src/new_account.mjml (100%) rename src/backend/app/{app => }/email-templates/src/reset_password.mjml (100%) rename src/backend/app/{app => }/email-templates/src/test_email.mjml (100%) rename src/backend/app/{app => }/initial_data.py (100%) rename src/backend/app/{app => }/main.py (100%) rename src/backend/app/{app => }/models.py (100%) rename src/backend/app/{app => }/schemas/__init__.py (100%) rename src/backend/app/{app => }/schemas/item.py (100%) rename src/backend/app/{app => }/schemas/msg.py (100%) rename src/backend/app/{app => }/schemas/token.py (100%) rename src/backend/app/{app => }/schemas/user.py (100%) rename src/backend/app/{app => }/tests/.gitignore (100%) rename src/backend/app/{app => }/tests/__init__.py (100%) rename src/backend/app/{app => }/tests/api/__init__.py (100%) rename src/backend/app/{app => }/tests/api/api_v1/__init__.py (100%) rename src/backend/app/{app => }/tests/api/api_v1/test_celery.py (100%) rename src/backend/app/{app => }/tests/api/api_v1/test_items.py (100%) rename src/backend/app/{app => }/tests/api/api_v1/test_login.py (100%) rename src/backend/app/{app => }/tests/api/api_v1/test_users.py (100%) rename src/backend/app/{app => }/tests/conftest.py (100%) rename src/backend/app/{app => }/tests/crud/__init__.py (100%) rename src/backend/app/{app => }/tests/crud/test_item.py (100%) rename src/backend/app/{app => }/tests/crud/test_user.py (100%) rename src/backend/app/{app => }/tests/utils/__init__.py (100%) rename src/backend/app/{app => }/tests/utils/item.py (100%) rename src/backend/app/{app => }/tests/utils/user.py (100%) rename src/backend/app/{app => }/tests/utils/utils.py (100%) rename src/backend/app/{app => }/tests_pre_start.py (100%) rename src/backend/app/{app => }/utils.py (100%) rename src/backend/app/{app => }/worker.py (100%) rename src/backend/{app => }/mypy.ini (100%) rename src/backend/{app => }/prestart.sh (100%) rename src/backend/{app => }/pyproject.toml (100%) rename src/backend/{app => }/scripts/format-imports.sh (100%) rename src/backend/{app => }/scripts/format.sh (100%) rename src/backend/{app => }/scripts/lint.sh (100%) rename src/backend/{app => }/scripts/test-cov-html.sh (100%) rename src/backend/{app => }/scripts/test.sh (100%) rename src/backend/{app => }/tests-start.sh (100%) rename src/backend/{app => }/worker-start.sh (100%) diff --git a/src/backend/.dockerignore b/src/backend/.dockerignore new file mode 100644 index 0000000000..c0de4abf73 --- /dev/null +++ b/src/backend/.dockerignore @@ -0,0 +1,8 @@ +# Python +__pycache__ +app.egg-info +*.pyc +.mypy_cache +.coverage +htmlcov +.venv diff --git a/src/backend/app/.flake8 b/src/backend/.flake8 similarity index 100% rename from src/backend/app/.flake8 rename to src/backend/.flake8 diff --git a/src/backend/.gitignore b/src/backend/.gitignore index 41d8e2e065..8078a84461 100644 --- a/src/backend/.gitignore +++ b/src/backend/.gitignore @@ -1,3 +1,6 @@ __pycache__ app.egg-info *.pyc +.mypy_cache +.coverage +htmlcov diff --git a/src/backend/app/alembic.ini b/src/backend/alembic.ini similarity index 98% rename from src/backend/app/alembic.ini rename to src/backend/alembic.ini index 921aaf17b8..24841c2bfb 100755 --- a/src/backend/app/alembic.ini +++ b/src/backend/alembic.ini @@ -2,7 +2,7 @@ [alembic] # path to migration scripts -script_location = alembic +script_location = app/alembic # template used to generate migration files # file_template = %%(rev)s_%%(slug)s diff --git a/src/backend/app/.gitignore b/src/backend/app/.gitignore deleted file mode 100644 index f511683016..0000000000 --- a/src/backend/app/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.mypy_cache -.coverage -htmlcov diff --git a/src/backend/app/app/__init__.py b/src/backend/app/__init__.py similarity index 100% rename from src/backend/app/app/__init__.py rename to src/backend/app/__init__.py diff --git a/src/backend/app/app/api/__init__.py b/src/backend/app/api/__init__.py similarity index 100% rename from src/backend/app/app/api/__init__.py rename to src/backend/app/api/__init__.py diff --git a/src/backend/app/app/api/api_v1/__init__.py b/src/backend/app/api/api_v1/__init__.py similarity index 100% rename from src/backend/app/app/api/api_v1/__init__.py rename to src/backend/app/api/api_v1/__init__.py diff --git a/src/backend/app/app/api/api_v1/api.py b/src/backend/app/api/api_v1/api.py similarity index 100% rename from src/backend/app/app/api/api_v1/api.py rename to src/backend/app/api/api_v1/api.py diff --git a/src/backend/app/app/api/api_v1/endpoints/__init__.py b/src/backend/app/api/api_v1/endpoints/__init__.py similarity index 100% rename from src/backend/app/app/api/api_v1/endpoints/__init__.py rename to src/backend/app/api/api_v1/endpoints/__init__.py diff --git a/src/backend/app/app/api/api_v1/endpoints/items.py b/src/backend/app/api/api_v1/endpoints/items.py similarity index 100% rename from src/backend/app/app/api/api_v1/endpoints/items.py rename to src/backend/app/api/api_v1/endpoints/items.py diff --git a/src/backend/app/app/api/api_v1/endpoints/login.py b/src/backend/app/api/api_v1/endpoints/login.py similarity index 100% rename from src/backend/app/app/api/api_v1/endpoints/login.py rename to src/backend/app/api/api_v1/endpoints/login.py diff --git a/src/backend/app/app/api/api_v1/endpoints/users.py b/src/backend/app/api/api_v1/endpoints/users.py similarity index 100% rename from src/backend/app/app/api/api_v1/endpoints/users.py rename to src/backend/app/api/api_v1/endpoints/users.py diff --git a/src/backend/app/app/api/api_v1/endpoints/utils.py b/src/backend/app/api/api_v1/endpoints/utils.py similarity index 100% rename from src/backend/app/app/api/api_v1/endpoints/utils.py rename to src/backend/app/api/api_v1/endpoints/utils.py diff --git a/src/backend/app/app/api/deps.py b/src/backend/app/api/deps.py similarity index 100% rename from src/backend/app/app/api/deps.py rename to src/backend/app/api/deps.py diff --git a/src/backend/app/app/backend_pre_start.py b/src/backend/app/backend_pre_start.py similarity index 100% rename from src/backend/app/app/backend_pre_start.py rename to src/backend/app/backend_pre_start.py diff --git a/src/backend/app/app/celeryworker_pre_start.py b/src/backend/app/celeryworker_pre_start.py similarity index 100% rename from src/backend/app/app/celeryworker_pre_start.py rename to src/backend/app/celeryworker_pre_start.py diff --git a/src/backend/app/app/core/__init__.py b/src/backend/app/core/__init__.py similarity index 100% rename from src/backend/app/app/core/__init__.py rename to src/backend/app/core/__init__.py diff --git a/src/backend/app/app/core/celery_app.py b/src/backend/app/core/celery_app.py similarity index 100% rename from src/backend/app/app/core/celery_app.py rename to src/backend/app/core/celery_app.py diff --git a/src/backend/app/app/core/config.py b/src/backend/app/core/config.py similarity index 100% rename from src/backend/app/app/core/config.py rename to src/backend/app/core/config.py diff --git a/src/backend/app/app/core/security.py b/src/backend/app/core/security.py similarity index 100% rename from src/backend/app/app/core/security.py rename to src/backend/app/core/security.py diff --git a/src/backend/app/app/crud/__init__.py b/src/backend/app/crud/__init__.py similarity index 100% rename from src/backend/app/app/crud/__init__.py rename to src/backend/app/crud/__init__.py diff --git a/src/backend/app/app/crud/base.py b/src/backend/app/crud/base.py similarity index 100% rename from src/backend/app/app/crud/base.py rename to src/backend/app/crud/base.py diff --git a/src/backend/app/app/crud/crud_item.py b/src/backend/app/crud/crud_item.py similarity index 100% rename from src/backend/app/app/crud/crud_item.py rename to src/backend/app/crud/crud_item.py diff --git a/src/backend/app/app/crud/crud_user.py b/src/backend/app/crud/crud_user.py similarity index 100% rename from src/backend/app/app/crud/crud_user.py rename to src/backend/app/crud/crud_user.py diff --git a/src/backend/app/app/db/__init__.py b/src/backend/app/db/__init__.py similarity index 100% rename from src/backend/app/app/db/__init__.py rename to src/backend/app/db/__init__.py diff --git a/src/backend/app/app/db/engine.py b/src/backend/app/db/engine.py similarity index 100% rename from src/backend/app/app/db/engine.py rename to src/backend/app/db/engine.py diff --git a/src/backend/app/app/db/init_db.py b/src/backend/app/db/init_db.py similarity index 100% rename from src/backend/app/app/db/init_db.py rename to src/backend/app/db/init_db.py diff --git a/src/backend/app/app/email-templates/build/new_account.html b/src/backend/app/email-templates/build/new_account.html similarity index 100% rename from src/backend/app/app/email-templates/build/new_account.html rename to src/backend/app/email-templates/build/new_account.html diff --git a/src/backend/app/app/email-templates/build/reset_password.html b/src/backend/app/email-templates/build/reset_password.html similarity index 100% rename from src/backend/app/app/email-templates/build/reset_password.html rename to src/backend/app/email-templates/build/reset_password.html diff --git a/src/backend/app/app/email-templates/build/test_email.html b/src/backend/app/email-templates/build/test_email.html similarity index 100% rename from src/backend/app/app/email-templates/build/test_email.html rename to src/backend/app/email-templates/build/test_email.html diff --git a/src/backend/app/app/email-templates/src/new_account.mjml b/src/backend/app/email-templates/src/new_account.mjml similarity index 100% rename from src/backend/app/app/email-templates/src/new_account.mjml rename to src/backend/app/email-templates/src/new_account.mjml diff --git a/src/backend/app/app/email-templates/src/reset_password.mjml b/src/backend/app/email-templates/src/reset_password.mjml similarity index 100% rename from src/backend/app/app/email-templates/src/reset_password.mjml rename to src/backend/app/email-templates/src/reset_password.mjml diff --git a/src/backend/app/app/email-templates/src/test_email.mjml b/src/backend/app/email-templates/src/test_email.mjml similarity index 100% rename from src/backend/app/app/email-templates/src/test_email.mjml rename to src/backend/app/email-templates/src/test_email.mjml diff --git a/src/backend/app/app/initial_data.py b/src/backend/app/initial_data.py similarity index 100% rename from src/backend/app/app/initial_data.py rename to src/backend/app/initial_data.py diff --git a/src/backend/app/app/main.py b/src/backend/app/main.py similarity index 100% rename from src/backend/app/app/main.py rename to src/backend/app/main.py diff --git a/src/backend/app/app/models.py b/src/backend/app/models.py similarity index 100% rename from src/backend/app/app/models.py rename to src/backend/app/models.py diff --git a/src/backend/app/app/schemas/__init__.py b/src/backend/app/schemas/__init__.py similarity index 100% rename from src/backend/app/app/schemas/__init__.py rename to src/backend/app/schemas/__init__.py diff --git a/src/backend/app/app/schemas/item.py b/src/backend/app/schemas/item.py similarity index 100% rename from src/backend/app/app/schemas/item.py rename to src/backend/app/schemas/item.py diff --git a/src/backend/app/app/schemas/msg.py b/src/backend/app/schemas/msg.py similarity index 100% rename from src/backend/app/app/schemas/msg.py rename to src/backend/app/schemas/msg.py diff --git a/src/backend/app/app/schemas/token.py b/src/backend/app/schemas/token.py similarity index 100% rename from src/backend/app/app/schemas/token.py rename to src/backend/app/schemas/token.py diff --git a/src/backend/app/app/schemas/user.py b/src/backend/app/schemas/user.py similarity index 100% rename from src/backend/app/app/schemas/user.py rename to src/backend/app/schemas/user.py diff --git a/src/backend/app/app/tests/.gitignore b/src/backend/app/tests/.gitignore similarity index 100% rename from src/backend/app/app/tests/.gitignore rename to src/backend/app/tests/.gitignore diff --git a/src/backend/app/app/tests/__init__.py b/src/backend/app/tests/__init__.py similarity index 100% rename from src/backend/app/app/tests/__init__.py rename to src/backend/app/tests/__init__.py diff --git a/src/backend/app/app/tests/api/__init__.py b/src/backend/app/tests/api/__init__.py similarity index 100% rename from src/backend/app/app/tests/api/__init__.py rename to src/backend/app/tests/api/__init__.py diff --git a/src/backend/app/app/tests/api/api_v1/__init__.py b/src/backend/app/tests/api/api_v1/__init__.py similarity index 100% rename from src/backend/app/app/tests/api/api_v1/__init__.py rename to src/backend/app/tests/api/api_v1/__init__.py diff --git a/src/backend/app/app/tests/api/api_v1/test_celery.py b/src/backend/app/tests/api/api_v1/test_celery.py similarity index 100% rename from src/backend/app/app/tests/api/api_v1/test_celery.py rename to src/backend/app/tests/api/api_v1/test_celery.py diff --git a/src/backend/app/app/tests/api/api_v1/test_items.py b/src/backend/app/tests/api/api_v1/test_items.py similarity index 100% rename from src/backend/app/app/tests/api/api_v1/test_items.py rename to src/backend/app/tests/api/api_v1/test_items.py diff --git a/src/backend/app/app/tests/api/api_v1/test_login.py b/src/backend/app/tests/api/api_v1/test_login.py similarity index 100% rename from src/backend/app/app/tests/api/api_v1/test_login.py rename to src/backend/app/tests/api/api_v1/test_login.py diff --git a/src/backend/app/app/tests/api/api_v1/test_users.py b/src/backend/app/tests/api/api_v1/test_users.py similarity index 100% rename from src/backend/app/app/tests/api/api_v1/test_users.py rename to src/backend/app/tests/api/api_v1/test_users.py diff --git a/src/backend/app/app/tests/conftest.py b/src/backend/app/tests/conftest.py similarity index 100% rename from src/backend/app/app/tests/conftest.py rename to src/backend/app/tests/conftest.py diff --git a/src/backend/app/app/tests/crud/__init__.py b/src/backend/app/tests/crud/__init__.py similarity index 100% rename from src/backend/app/app/tests/crud/__init__.py rename to src/backend/app/tests/crud/__init__.py diff --git a/src/backend/app/app/tests/crud/test_item.py b/src/backend/app/tests/crud/test_item.py similarity index 100% rename from src/backend/app/app/tests/crud/test_item.py rename to src/backend/app/tests/crud/test_item.py diff --git a/src/backend/app/app/tests/crud/test_user.py b/src/backend/app/tests/crud/test_user.py similarity index 100% rename from src/backend/app/app/tests/crud/test_user.py rename to src/backend/app/tests/crud/test_user.py diff --git a/src/backend/app/app/tests/utils/__init__.py b/src/backend/app/tests/utils/__init__.py similarity index 100% rename from src/backend/app/app/tests/utils/__init__.py rename to src/backend/app/tests/utils/__init__.py diff --git a/src/backend/app/app/tests/utils/item.py b/src/backend/app/tests/utils/item.py similarity index 100% rename from src/backend/app/app/tests/utils/item.py rename to src/backend/app/tests/utils/item.py diff --git a/src/backend/app/app/tests/utils/user.py b/src/backend/app/tests/utils/user.py similarity index 100% rename from src/backend/app/app/tests/utils/user.py rename to src/backend/app/tests/utils/user.py diff --git a/src/backend/app/app/tests/utils/utils.py b/src/backend/app/tests/utils/utils.py similarity index 100% rename from src/backend/app/app/tests/utils/utils.py rename to src/backend/app/tests/utils/utils.py diff --git a/src/backend/app/app/tests_pre_start.py b/src/backend/app/tests_pre_start.py similarity index 100% rename from src/backend/app/app/tests_pre_start.py rename to src/backend/app/tests_pre_start.py diff --git a/src/backend/app/app/utils.py b/src/backend/app/utils.py similarity index 100% rename from src/backend/app/app/utils.py rename to src/backend/app/utils.py diff --git a/src/backend/app/app/worker.py b/src/backend/app/worker.py similarity index 100% rename from src/backend/app/app/worker.py rename to src/backend/app/worker.py diff --git a/src/backend/backend.dockerfile b/src/backend/backend.dockerfile index 9896610a86..44ff6ffacd 100644 --- a/src/backend/backend.dockerfile +++ b/src/backend/backend.dockerfile @@ -9,17 +9,18 @@ RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python poetry config virtualenvs.create false # Copy poetry.lock* in case it doesn't exist in the repo -COPY ./app/pyproject.toml ./app/poetry.lock* /app/ +COPY ./pyproject.toml ./poetry.lock* /app/ # Allow installing dev dependencies to run tests ARG INSTALL_DEV=false RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; else poetry install --no-root --only main ; fi" -# For development, Jupyter remote kernel, Hydrogen -# Using inside the container: -# jupyter lab --ip=0.0.0.0 --allow-root --NotebookApp.custom_display_url=http://127.0.0.1:8888 -ARG INSTALL_JUPYTER=false -RUN bash -c "if [ $INSTALL_JUPYTER == 'true' ] ; then pip install jupyterlab ; fi" - -COPY ./app /app ENV PYTHONPATH=/app + +COPY ./alembic.ini /app/ + +COPY ./prestart.sh /app/ + +COPY ./tests-start.sh /app/ + +COPY ./app /app/app diff --git a/src/backend/celeryworker.dockerfile b/src/backend/celeryworker.dockerfile index 21b6a8e0f7..5ad5938c00 100644 --- a/src/backend/celeryworker.dockerfile +++ b/src/backend/celeryworker.dockerfile @@ -9,26 +9,21 @@ RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python poetry config virtualenvs.create false # Copy poetry.lock* in case it doesn't exist in the repo -COPY ./app/pyproject.toml ./app/poetry.lock* /app/ +COPY ./pyproject.toml ./poetry.lock* /app/ # Allow installing dev dependencies to run tests ARG INSTALL_DEV=false RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; else poetry install --no-root --only main ; fi" -# For development, Jupyter remote kernel, Hydrogen -# Using inside the container: -# jupyter lab --ip=0.0.0.0 --allow-root --NotebookApp.custom_display_url=http://127.0.0.1:8888 -ARG INSTALL_JUPYTER=false -RUN bash -c "if [ $INSTALL_JUPYTER == 'true' ] ; then pip install jupyterlab ; fi" - ENV C_FORCE_ROOT=1 -COPY ./app /app -WORKDIR /app - ENV PYTHONPATH=/app -COPY ./app/worker-start.sh /worker-start.sh +COPY ./alembic.ini /app/ + +COPY ./worker-start.sh /worker-start.sh + +COPY ./app /app/app RUN chmod +x /worker-start.sh diff --git a/src/backend/app/mypy.ini b/src/backend/mypy.ini similarity index 100% rename from src/backend/app/mypy.ini rename to src/backend/mypy.ini diff --git a/src/backend/app/prestart.sh b/src/backend/prestart.sh similarity index 100% rename from src/backend/app/prestart.sh rename to src/backend/prestart.sh diff --git a/src/backend/app/pyproject.toml b/src/backend/pyproject.toml similarity index 100% rename from src/backend/app/pyproject.toml rename to src/backend/pyproject.toml diff --git a/src/backend/app/scripts/format-imports.sh b/src/backend/scripts/format-imports.sh similarity index 100% rename from src/backend/app/scripts/format-imports.sh rename to src/backend/scripts/format-imports.sh diff --git a/src/backend/app/scripts/format.sh b/src/backend/scripts/format.sh similarity index 100% rename from src/backend/app/scripts/format.sh rename to src/backend/scripts/format.sh diff --git a/src/backend/app/scripts/lint.sh b/src/backend/scripts/lint.sh similarity index 100% rename from src/backend/app/scripts/lint.sh rename to src/backend/scripts/lint.sh diff --git a/src/backend/app/scripts/test-cov-html.sh b/src/backend/scripts/test-cov-html.sh similarity index 100% rename from src/backend/app/scripts/test-cov-html.sh rename to src/backend/scripts/test-cov-html.sh diff --git a/src/backend/app/scripts/test.sh b/src/backend/scripts/test.sh similarity index 100% rename from src/backend/app/scripts/test.sh rename to src/backend/scripts/test.sh diff --git a/src/backend/app/tests-start.sh b/src/backend/tests-start.sh similarity index 100% rename from src/backend/app/tests-start.sh rename to src/backend/tests-start.sh diff --git a/src/backend/app/worker-start.sh b/src/backend/worker-start.sh similarity index 100% rename from src/backend/app/worker-start.sh rename to src/backend/worker-start.sh diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 6e12f41305..4dde2a4ca7 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -49,32 +49,28 @@ services: ports: - "8888:8888" volumes: - - ./backend/app:/app + - ./backend/:/app environment: - - JUPYTER=jupyter lab --ip=0.0.0.0 --allow-root --NotebookApp.custom_display_url=http://127.0.0.1:8888 - SERVER_HOST=http://${DOMAIN?Variable not set} build: context: ./backend dockerfile: backend.dockerfile args: INSTALL_DEV: ${INSTALL_DEV-true} - INSTALL_JUPYTER: ${INSTALL_JUPYTER-true} # command: sleep infinity # Infinite loop to keep container alive doing nothing command: /start-reload.sh celeryworker: volumes: - - ./backend/app:/app + - ./backend/:/app environment: - RUN=celery worker -A app.worker -l info -Q main-queue -c 1 - - JUPYTER=jupyter lab --ip=0.0.0.0 --allow-root --NotebookApp.custom_display_url=http://127.0.0.1:8888 - SERVER_HOST=http://${DOMAIN?Variable not set} build: context: ./backend dockerfile: celeryworker.dockerfile args: INSTALL_DEV: ${INSTALL_DEV-true} - INSTALL_JUPYTER: ${INSTALL_JUPYTER-true} frontend: build: From a766f4d03f504229d8b2f121c7d640347933992a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Feb 2024 19:29:00 +0100 Subject: [PATCH 177/771] =?UTF-8?q?=E2=9E=95=20Replace=20black,=20isort,?= =?UTF-8?q?=20flake8,=20autoflake=20with=20ruff=20and=20upgrade=20mypy=20(?= =?UTF-8?q?#610)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 25 +++++++++++++++++++++++++ src/backend/.flake8 | 3 --- src/backend/mypy.ini | 4 ---- src/backend/pyproject.toml | 35 ++++++++++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 src/backend/.flake8 delete mode 100644 src/backend/mypy.ini diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..984c6caa0a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +default_language_version: + python: python3.10 +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + - id: check-toml + - id: check-yaml + args: + - --unsafe + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.2.2 + hooks: + - id: ruff + args: + - --fix + - id: ruff-format +ci: + autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks + autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate diff --git a/src/backend/.flake8 b/src/backend/.flake8 deleted file mode 100644 index 710dc9c030..0000000000 --- a/src/backend/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 88 -exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache diff --git a/src/backend/mypy.ini b/src/backend/mypy.ini deleted file mode 100644 index 9813db029b..0000000000 --- a/src/backend/mypy.ini +++ /dev/null @@ -1,4 +0,0 @@ -[mypy] -plugins = pydantic.mypy, sqlmypy -ignore_missing_imports = True -disallow_untyped_defs = True diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index e15178463d..7e10822872 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -27,13 +27,11 @@ sqlmodel = "^0.0.16" bcrypt = "4.0.1" [tool.poetry.group.dev.dependencies] -mypy = "^1.7.0" -black = "^23.11.0" -isort = "^5.12.0" -autoflake = "^2.2.1" -flake8 = "^6.1.0" pytest = "^7.4.3" pytest-cov = "^4.1.0" +mypy = "^1.8.0" +ruff = "^0.2.2" +pre-commit = "^3.6.2" [tool.isort] multi_line_output = 3 @@ -43,3 +41,30 @@ line_length = 88 [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" + +[tool.mypy] +strict = true + +[tool.ruff] +target-version = "py310" + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade +] +ignore = [ + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "W191", # indentation contains tabs + "B904", # Allow raising exceptions without from e, for HTTPException +] + +[tool.ruff.lint.pyupgrade] +# Preserve types, even if a file imports `from __future__ import annotations`. +keep-runtime-typing = true From b6bfee311e8cb834b0d89a3d4b8ac26b8f859f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Feb 2024 19:39:33 +0100 Subject: [PATCH 178/771] =?UTF-8?q?=F0=9F=8E=A8=20Format=20files=20with=20?= =?UTF-8?q?pre-commit=20and=20Ruff=20(#611)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- src/backend/app/alembic/README | 2 +- src/backend/app/alembic/env.py | 8 +-- .../e2412789c190_initialize_models.py | 52 +++++++++++-------- src/backend/app/api/api_v1/endpoints/items.py | 6 +-- src/backend/app/api/api_v1/endpoints/users.py | 8 ++- src/backend/app/api/deps.py | 3 +- src/backend/app/core/config.py | 32 ++++++------ src/backend/app/core/security.py | 6 +-- src/backend/app/crud/__init__.py | 19 ++++--- src/backend/app/crud/base.py | 8 +-- src/backend/app/crud/crud_item.py | 4 +- src/backend/app/crud/crud_user.py | 8 +-- .../email-templates/build/new_account.html | 2 +- .../email-templates/build/reset_password.html | 2 +- .../app/email-templates/build/test_email.html | 2 +- src/backend/app/models.py | 30 +++++------ src/backend/app/schemas/item.py | 6 +-- src/backend/app/schemas/token.py | 4 +- src/backend/app/schemas/user.py | 12 ++--- .../app/tests/api/api_v1/test_celery.py | 4 +- .../app/tests/api/api_v1/test_items.py | 7 ++- .../app/tests/api/api_v1/test_login.py | 7 ++- .../app/tests/api/api_v1/test_users.py | 23 ++++---- src/backend/app/tests/conftest.py | 6 +-- src/backend/app/tests/utils/item.py | 4 +- src/backend/app/tests/utils/user.py | 6 +-- src/backend/app/tests/utils/utils.py | 3 +- src/backend/app/utils.py | 19 ++++--- src/docker-compose.override.yml | 2 +- src/docker-compose.yml | 12 ++--- src/frontend/.nvmrc | 2 +- src/frontend/babel.config.js | 2 +- src/frontend/nginx.conf | 4 +- .../main/profile/UserProfileEditPassword.vue | 2 +- 35 files changed, 156 insertions(+), 163 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aabe1888d5..491acb17f7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: - name: Run tests run: docker compose build - - name: Docker Compose remove old containers and volumes + - name: Docker Compose remove old containers and volumes run: docker compose down -v --remove-orphans - name: Docker Compose up run: docker compose up -d diff --git a/src/backend/app/alembic/README b/src/backend/app/alembic/README index 98e4f9c44e..2500aa1bcf 100755 --- a/src/backend/app/alembic/README +++ b/src/backend/app/alembic/README @@ -1 +1 @@ -Generic single-database configuration. \ No newline at end of file +Generic single-database configuration. diff --git a/src/backend/app/alembic/env.py b/src/backend/app/alembic/env.py index 3e002a22e1..c146cf64ab 100755 --- a/src/backend/app/alembic/env.py +++ b/src/backend/app/alembic/env.py @@ -1,10 +1,8 @@ -from __future__ import with_statement - import os +from logging.config import fileConfig from alembic import context from sqlalchemy import engine_from_config, pool -from logging.config import fileConfig # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -69,7 +67,9 @@ def run_migrations_online(): configuration = config.get_section(config.config_ini_section) configuration["sqlalchemy.url"] = get_url() connectable = engine_from_config( - configuration, prefix="sqlalchemy.", poolclass=pool.NullPool, + configuration, + prefix="sqlalchemy.", + poolclass=pool.NullPool, ) with connectable.connect() as connection: diff --git a/src/backend/app/alembic/versions/e2412789c190_initialize_models.py b/src/backend/app/alembic/versions/e2412789c190_initialize_models.py index 7232ed44b1..7529ea91fa 100644 --- a/src/backend/app/alembic/versions/e2412789c190_initialize_models.py +++ b/src/backend/app/alembic/versions/e2412789c190_initialize_models.py @@ -1,17 +1,16 @@ """Initialize models Revision ID: e2412789c190 -Revises: +Revises: Create Date: 2023-11-24 22:55:43.195942 """ -from alembic import op import sqlalchemy as sa import sqlmodel.sql.sqltypes - +from alembic import op # revision identifiers, used by Alembic. -revision = 'e2412789c190' +revision = "e2412789c190" down_revision = None branch_labels = None depends_on = None @@ -19,30 +18,37 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('is_superuser', sa.Boolean(), nullable=False), - sa.Column('full_name', sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('hashed_password', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + "user", + sa.Column("email", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("is_superuser", sa.Boolean(), nullable=False), + sa.Column("full_name", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column( + "hashed_password", sqlmodel.sql.sqltypes.AutoString(), nullable=False + ), + sa.PrimaryKeyConstraint("id"), ) - op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) - op.create_table('item', - sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('owner_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True) + op.create_table( + "item", + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("owner_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["owner_id"], + ["user.id"], + ), + sa.PrimaryKeyConstraint("id"), ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('item') - op.drop_index(op.f('ix_user_email'), table_name='user') - op.drop_table('user') + op.drop_table("item") + op.drop_index(op.f("ix_user_email"), table_name="user") + op.drop_table("user") # ### end Alembic commands ### diff --git a/src/backend/app/api/api_v1/endpoints/items.py b/src/backend/app/api/api_v1/endpoints/items.py index 9878c09caf..ddbe93eef5 100644 --- a/src/backend/app/api/api_v1/endpoints/items.py +++ b/src/backend/app/api/api_v1/endpoints/items.py @@ -1,10 +1,10 @@ from typing import Any from fastapi import APIRouter, HTTPException -from sqlmodel import select, func +from sqlmodel import func, select from app.api.deps import CurrentUser, SessionDep -from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message, ItemsOut +from app.models import Item, ItemCreate, ItemOut, ItemsOut, ItemUpdate, Message router = APIRouter() @@ -22,7 +22,7 @@ def read_items( if current_user.is_superuser: statement = select(Item).offset(skip).limit(limit) - items = session.exec(statement).all() + items = session.exec(statement).all() else: statement = ( select(Item) diff --git a/src/backend/app/api/api_v1/endpoints/users.py b/src/backend/app/api/api_v1/endpoints/users.py index 0e71f1f854..0b9a51127e 100644 --- a/src/backend/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/api/api_v1/endpoints/users.py @@ -1,7 +1,7 @@ -from typing import Any, List +from typing import Any from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import select, func +from sqlmodel import func, select from app import crud from app.api.deps import ( @@ -28,9 +28,7 @@ @router.get( - "/", - dependencies=[Depends(get_current_active_superuser)], - response_model=UsersOut + "/", dependencies=[Depends(get_current_active_superuser)], response_model=UsersOut ) def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: """ diff --git a/src/backend/app/api/deps.py b/src/backend/app/api/deps.py index abe067b55f..1810ce0820 100644 --- a/src/backend/app/api/deps.py +++ b/src/backend/app/api/deps.py @@ -1,4 +1,5 @@ -from typing import Annotated, Generator +from collections.abc import Generator +from typing import Annotated from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer diff --git a/src/backend/app/core/config.py b/src/backend/app/core/config.py index 5aa0f8354c..abc96e5d2a 100644 --- a/src/backend/app/core/config.py +++ b/src/backend/app/core/config.py @@ -1,5 +1,5 @@ import secrets -from typing import Any, Dict, List, Optional, Union +from typing import Any from pydantic import AnyHttpUrl, BaseSettings, EmailStr, HttpUrl, PostgresDsn, validator @@ -14,21 +14,21 @@ class Settings(BaseSettings): # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \ # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]' - BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = [] + BACKEND_CORS_ORIGINS: list[AnyHttpUrl] = [] @validator("BACKEND_CORS_ORIGINS", pre=True) - def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]: + def assemble_cors_origins(cls, v: str | list[str]) -> list[str] | str: if isinstance(v, str) and not v.startswith("["): return [i.strip() for i in v.split(",")] - elif isinstance(v, (list, str)): + elif isinstance(v, list | str): return v raise ValueError(v) PROJECT_NAME: str - SENTRY_DSN: Optional[HttpUrl] = None + SENTRY_DSN: HttpUrl | None = None @validator("SENTRY_DSN", pre=True) - def sentry_dsn_can_be_blank(cls, v: str) -> Optional[str]: + def sentry_dsn_can_be_blank(cls, v: str) -> str | None: if len(v) == 0: return None return v @@ -37,10 +37,10 @@ def sentry_dsn_can_be_blank(cls, v: str) -> Optional[str]: POSTGRES_USER: str POSTGRES_PASSWORD: str POSTGRES_DB: str - SQLALCHEMY_DATABASE_URI: Optional[PostgresDsn] = None + SQLALCHEMY_DATABASE_URI: PostgresDsn | None = None @validator("SQLALCHEMY_DATABASE_URI", pre=True) - def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any: + def assemble_db_connection(cls, v: str | None, values: dict[str, Any]) -> Any: if isinstance(v, str): return v return PostgresDsn.build( @@ -52,15 +52,15 @@ def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any ) SMTP_TLS: bool = True - SMTP_PORT: Optional[int] = None - SMTP_HOST: Optional[str] = None - SMTP_USER: Optional[str] = None - SMTP_PASSWORD: Optional[str] = None - EMAILS_FROM_EMAIL: Optional[EmailStr] = None - EMAILS_FROM_NAME: Optional[str] = None + SMTP_PORT: int | None = None + SMTP_HOST: str | None = None + SMTP_USER: str | None = None + SMTP_PASSWORD: str | None = None + EMAILS_FROM_EMAIL: EmailStr | None = None + EMAILS_FROM_NAME: str | None = None @validator("EMAILS_FROM_NAME") - def get_project_name(cls, v: Optional[str], values: Dict[str, Any]) -> str: + def get_project_name(cls, v: str | None, values: dict[str, Any]) -> str: if not v: return values["PROJECT_NAME"] return v @@ -70,7 +70,7 @@ def get_project_name(cls, v: Optional[str], values: Dict[str, Any]) -> str: EMAILS_ENABLED: bool = False @validator("EMAILS_ENABLED", pre=True) - def get_emails_enabled(cls, v: bool, values: Dict[str, Any]) -> bool: + def get_emails_enabled(cls, v: bool, values: dict[str, Any]) -> bool: return bool( values.get("SMTP_HOST") and values.get("SMTP_PORT") diff --git a/src/backend/app/core/security.py b/src/backend/app/core/security.py index 6c6ee8bc30..c9e5085f08 100644 --- a/src/backend/app/core/security.py +++ b/src/backend/app/core/security.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Any, Union +from typing import Any from jose import jwt from passlib.context import CryptContext @@ -12,9 +12,7 @@ ALGORITHM = "HS256" -def create_access_token( - subject: Union[str, Any], expires_delta: timedelta = None -) -> str: +def create_access_token(subject: str | Any, expires_delta: timedelta = None) -> str: if expires_delta: expire = datetime.utcnow() + expires_delta else: diff --git a/src/backend/app/crud/__init__.py b/src/backend/app/crud/__init__.py index d452138321..ee949e562d 100644 --- a/src/backend/app/crud/__init__.py +++ b/src/backend/app/crud/__init__.py @@ -1,16 +1,15 @@ -from .crud_item import item -from .crud_user import user - # For a new basic set of CRUD operations you could just do - # from .base import CRUDBase # from app.models.item import Item # from app.schemas.item import ItemCreate, ItemUpdate - # item = CRUDBase[Item, ItemCreate, ItemUpdate](Item) from sqlmodel import Session, select + from app.core.security import get_password_hash, verify_password -from app.models import UserCreate, User +from app.models import User, UserCreate + +from .crud_item import item as item +from .crud_user import user as user def create_user(*, session: Session, user_create: UserCreate) -> User: @@ -30,9 +29,9 @@ def get_user_by_email(*, session: Session, email: str) -> User | None: def authenticate(*, session: Session, email: str, password: str) -> User | None: - user = get_user_by_email(session=session, email=email) - if not user: + db_user = get_user_by_email(session=session, email=email) + if not db_user: return None - if not verify_password(password, user.hashed_password): + if not verify_password(password, db_user.hashed_password): return None - return user + return db_user diff --git a/src/backend/app/crud/base.py b/src/backend/app/crud/base.py index da93c49a70..6556e3ca74 100644 --- a/src/backend/app/crud/base.py +++ b/src/backend/app/crud/base.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union +from typing import Any, Generic, TypeVar from fastapi.encoders import jsonable_encoder from pydantic import BaseModel @@ -10,7 +10,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): - def __init__(self, model: Type[ModelType]): + def __init__(self, model: type[ModelType]): """ CRUD object with default methods to Create, Read, Update, Delete (CRUD). @@ -21,7 +21,7 @@ def __init__(self, model: Type[ModelType]): """ self.model = model - def get(self, db: Session, id: Any) -> Optional[ModelType]: + def get(self, db: Session, id: Any) -> ModelType | None: return db.query(self.model).filter(self.model.id == id).first() def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: @@ -37,7 +37,7 @@ def update( db: Session, *, db_obj: ModelType, - obj_in: Union[UpdateSchemaType, Dict[str, Any]] + obj_in: UpdateSchemaType | dict[str, Any], ) -> ModelType: obj_data = jsonable_encoder(db_obj) if isinstance(obj_in, dict): diff --git a/src/backend/app/crud/crud_item.py b/src/backend/app/crud/crud_item.py index c62d80e5b4..02c5060b1b 100644 --- a/src/backend/app/crud/crud_item.py +++ b/src/backend/app/crud/crud_item.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi.encoders import jsonable_encoder from sqlalchemy.orm import Session @@ -21,7 +19,7 @@ def create_with_owner( def get_multi_by_owner( self, db: Session, *, owner_id: int, skip: int = 0, limit: int = 100 - ) -> List[Item]: + ) -> list[Item]: return ( db.query(self.model) .filter(Item.owner_id == owner_id) diff --git a/src/backend/app/crud/crud_user.py b/src/backend/app/crud/crud_user.py index 1f2509b93c..b444502db5 100644 --- a/src/backend/app/crud/crud_user.py +++ b/src/backend/app/crud/crud_user.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Union +from typing import Any from sqlalchemy.orm import Session @@ -9,7 +9,7 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): - def get_by_email(self, db: Session, *, email: str) -> Optional[User]: + def get_by_email(self, db: Session, *, email: str) -> User | None: return db.query(User).filter(User.email == email).first() def create(self, db: Session, *, obj_in: UserCreate) -> User: @@ -25,7 +25,7 @@ def create(self, db: Session, *, obj_in: UserCreate) -> User: return db_obj def update( - self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]] + self, db: Session, *, db_obj: User, obj_in: UserUpdate | dict[str, Any] ) -> User: if isinstance(obj_in, dict): update_data = obj_in @@ -37,7 +37,7 @@ def update( update_data["hashed_password"] = hashed_password return super().update(db, db_obj=db_obj, obj_in=update_data) - def authenticate(self, db: Session, *, email: str, password: str) -> Optional[User]: + def authenticate(self, db: Session, *, email: str, password: str) -> User | None: user = self.get_by_email(db, email=email) if not user: return None diff --git a/src/backend/app/email-templates/build/new_account.html b/src/backend/app/email-templates/build/new_account.html index 395c7bd156..9d82b563e8 100644 --- a/src/backend/app/email-templates/build/new_account.html +++ b/src/backend/app/email-templates/build/new_account.html @@ -23,4 +23,4 @@ .mj-column-per-100 { width:100% !important; max-width: 100%; } }

{{ project_name }} - New Account
You have a new account:
Username: {{ username }}
Password: {{ password }}
Go to Dashboard

\ No newline at end of file + diff --git a/src/backend/app/email-templates/build/reset_password.html b/src/backend/app/email-templates/build/reset_password.html index 7fbf368b9f..888ce3bc52 100644 --- a/src/backend/app/email-templates/build/reset_password.html +++ b/src/backend/app/email-templates/build/reset_password.html @@ -23,4 +23,4 @@ .mj-column-per-100 { width:100% !important; max-width: 100%; } }

{{ project_name }} - Password Recovery
We received a request to recover the password for user {{ username }} with email {{ email }}
Reset your password by clicking the button below:
Reset Password
Or open the following link:

The reset password link / button will expire in {{ valid_hours }} hours.
If you didn't request a password recovery you can disregard this email.
\ No newline at end of file +
The reset password link / button will expire in {{ valid_hours }} hours.
If you didn't request a password recovery you can disregard this email.
diff --git a/src/backend/app/email-templates/build/test_email.html b/src/backend/app/email-templates/build/test_email.html index 294d576623..da78c57b11 100644 --- a/src/backend/app/email-templates/build/test_email.html +++ b/src/backend/app/email-templates/build/test_email.html @@ -22,4 +22,4 @@

{{ project_name }}
Test email for: {{ email }}
\ No newline at end of file +
{{ project_name }}
Test email for: {{ email }}
diff --git a/src/backend/app/models.py b/src/backend/app/models.py index 5331a96b1f..1b837ef414 100644 --- a/src/backend/app/models.py +++ b/src/backend/app/models.py @@ -1,5 +1,3 @@ -from typing import Union - from pydantic import EmailStr from sqlmodel import Field, Relationship, SQLModel @@ -9,7 +7,7 @@ class UserBase(SQLModel): email: EmailStr = Field(unique=True, index=True) is_active: bool = True is_superuser: bool = False - full_name: Union[str, None] = None + full_name: str | None = None # Properties to receive via API on creation @@ -20,18 +18,18 @@ class UserCreate(UserBase): class UserCreateOpen(SQLModel): email: EmailStr password: str - full_name: Union[str, None] = None + full_name: str | None = None # Properties to receive via API on update, all are optional class UserUpdate(UserBase): - email: Union[EmailStr, None] = None - password: Union[str, None] = None + email: EmailStr | None = None + password: str | None = None class UserUpdateMe(SQLModel): - full_name: Union[str, None] = None - email: Union[EmailStr, None] = None + full_name: str | None = None + email: EmailStr | None = None class UpdatePassword(SQLModel): @@ -41,7 +39,7 @@ class UpdatePassword(SQLModel): # Database model, database table inferred from class name class User(UserBase, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) + id: int | None = Field(default=None, primary_key=True) hashed_password: str items: list["Item"] = Relationship(back_populates="owner") @@ -59,7 +57,7 @@ class UsersOut(SQLModel): # Shared properties class ItemBase(SQLModel): title: str - description: Union[str, None] = None + description: str | None = None # Properties to receive on item creation @@ -69,17 +67,15 @@ class ItemCreate(ItemBase): # Properties to receive on item update class ItemUpdate(ItemBase): - title: Union[str, None] = None + title: str | None = None # Database model, database table inferred from class name class Item(ItemBase, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) + id: int | None = Field(default=None, primary_key=True) title: str - owner_id: Union[int, None] = Field( - default=None, foreign_key="user.id", nullable=False - ) - owner: Union[User, None] = Relationship(back_populates="items") + owner_id: int | None = Field(default=None, foreign_key="user.id", nullable=False) + owner: User | None = Relationship(back_populates="items") # Properties to return via API, id is always required @@ -106,7 +102,7 @@ class Token(SQLModel): # Contents of JWT token class TokenPayload(SQLModel): - sub: Union[int, None] = None + sub: int | None = None class NewPassword(SQLModel): diff --git a/src/backend/app/schemas/item.py b/src/backend/app/schemas/item.py index ac992cf2b1..5d3e6918ec 100644 --- a/src/backend/app/schemas/item.py +++ b/src/backend/app/schemas/item.py @@ -1,12 +1,10 @@ -from typing import Optional - from pydantic import BaseModel # Shared properties class ItemBase(BaseModel): - title: Optional[str] = None - description: Optional[str] = None + title: str | None = None + description: str | None = None # Properties to receive on item creation diff --git a/src/backend/app/schemas/token.py b/src/backend/app/schemas/token.py index ea85b460da..17c74027e5 100644 --- a/src/backend/app/schemas/token.py +++ b/src/backend/app/schemas/token.py @@ -1,5 +1,3 @@ -from typing import Optional - from pydantic import BaseModel @@ -9,4 +7,4 @@ class Token(BaseModel): class TokenPayload(BaseModel): - sub: Optional[int] = None + sub: int | None = None diff --git a/src/backend/app/schemas/user.py b/src/backend/app/schemas/user.py index 7f5c85ac68..0601e8d09b 100644 --- a/src/backend/app/schemas/user.py +++ b/src/backend/app/schemas/user.py @@ -1,14 +1,12 @@ -from typing import Optional - from pydantic import BaseModel, EmailStr # Shared properties class UserBase(BaseModel): - email: Optional[EmailStr] = None - is_active: Optional[bool] = True + email: EmailStr | None = None + is_active: bool | None = True is_superuser: bool = False - full_name: Optional[str] = None + full_name: str | None = None # Properties to receive via API on creation @@ -19,11 +17,11 @@ class UserCreate(UserBase): # Properties to receive via API on update class UserUpdate(UserBase): - password: Optional[str] = None + password: str | None = None class UserInDBBase(UserBase): - id: Optional[int] = None + id: int | None = None class Config: orm_mode = True diff --git a/src/backend/app/tests/api/api_v1/test_celery.py b/src/backend/app/tests/api/api_v1/test_celery.py index 80973064ff..bb1becdd06 100644 --- a/src/backend/app/tests/api/api_v1/test_celery.py +++ b/src/backend/app/tests/api/api_v1/test_celery.py @@ -1,12 +1,10 @@ -from typing import Dict - from fastapi.testclient import TestClient from app.core.config import settings def test_celery_worker_test( - client: TestClient, superuser_token_headers: Dict[str, str] + client: TestClient, superuser_token_headers: dict[str, str] ) -> None: data = {"message": "test"} r = client.post( diff --git a/src/backend/app/tests/api/api_v1/test_items.py b/src/backend/app/tests/api/api_v1/test_items.py index 6d799b62d4..f9abdcb102 100644 --- a/src/backend/app/tests/api/api_v1/test_items.py +++ b/src/backend/app/tests/api/api_v1/test_items.py @@ -10,7 +10,9 @@ def test_create_item( ) -> None: data = {"title": "Foo", "description": "Fighters"} response = client.post( - f"{settings.API_V1_STR}/items/", headers=superuser_token_headers, json=data, + f"{settings.API_V1_STR}/items/", + headers=superuser_token_headers, + json=data, ) assert response.status_code == 200 content = response.json() @@ -25,7 +27,8 @@ def test_read_item( ) -> None: item = create_random_item(db) response = client.get( - f"{settings.API_V1_STR}/items/{item.id}", headers=superuser_token_headers, + f"{settings.API_V1_STR}/items/{item.id}", + headers=superuser_token_headers, ) assert response.status_code == 200 content = response.json() diff --git a/src/backend/app/tests/api/api_v1/test_login.py b/src/backend/app/tests/api/api_v1/test_login.py index fd2c65a622..a8b33223b6 100644 --- a/src/backend/app/tests/api/api_v1/test_login.py +++ b/src/backend/app/tests/api/api_v1/test_login.py @@ -1,5 +1,3 @@ -from typing import Dict - from fastapi.testclient import TestClient from app.core.config import settings @@ -18,10 +16,11 @@ def test_get_access_token(client: TestClient) -> None: def test_use_access_token( - client: TestClient, superuser_token_headers: Dict[str, str] + client: TestClient, superuser_token_headers: dict[str, str] ) -> None: r = client.post( - f"{settings.API_V1_STR}/login/test-token", headers=superuser_token_headers, + f"{settings.API_V1_STR}/login/test-token", + headers=superuser_token_headers, ) result = r.json() assert r.status_code == 200 diff --git a/src/backend/app/tests/api/api_v1/test_users.py b/src/backend/app/tests/api/api_v1/test_users.py index 44d97f6649..adc97e0e0a 100644 --- a/src/backend/app/tests/api/api_v1/test_users.py +++ b/src/backend/app/tests/api/api_v1/test_users.py @@ -1,5 +1,3 @@ -from typing import Dict - from fastapi.testclient import TestClient from sqlalchemy.orm import Session @@ -10,7 +8,7 @@ def test_get_users_superuser_me( - client: TestClient, superuser_token_headers: Dict[str, str] + client: TestClient, superuser_token_headers: dict[str, str] ) -> None: r = client.get(f"{settings.API_V1_STR}/users/me", headers=superuser_token_headers) current_user = r.json() @@ -21,7 +19,7 @@ def test_get_users_superuser_me( def test_get_users_normal_user_me( - client: TestClient, normal_user_token_headers: Dict[str, str] + client: TestClient, normal_user_token_headers: dict[str, str] ) -> None: r = client.get(f"{settings.API_V1_STR}/users/me", headers=normal_user_token_headers) current_user = r.json() @@ -38,7 +36,9 @@ def test_create_user_new_email( password = random_lower_string() data = {"email": username, "password": password} r = client.post( - f"{settings.API_V1_STR}/users/", headers=superuser_token_headers, json=data, + f"{settings.API_V1_STR}/users/", + headers=superuser_token_headers, + json=data, ) assert 200 <= r.status_code < 300 created_user = r.json() @@ -56,7 +56,8 @@ def test_get_existing_user( user = crud.user.create(db, obj_in=user_in) user_id = user.id r = client.get( - f"{settings.API_V1_STR}/users/{user_id}", headers=superuser_token_headers, + f"{settings.API_V1_STR}/users/{user_id}", + headers=superuser_token_headers, ) assert 200 <= r.status_code < 300 api_user = r.json() @@ -75,7 +76,9 @@ def test_create_user_existing_username( crud.user.create(db, obj_in=user_in) data = {"email": username, "password": password} r = client.post( - f"{settings.API_V1_STR}/users/", headers=superuser_token_headers, json=data, + f"{settings.API_V1_STR}/users/", + headers=superuser_token_headers, + json=data, ) created_user = r.json() assert r.status_code == 400 @@ -83,13 +86,15 @@ def test_create_user_existing_username( def test_create_user_by_normal_user( - client: TestClient, normal_user_token_headers: Dict[str, str] + client: TestClient, normal_user_token_headers: dict[str, str] ) -> None: username = random_email() password = random_lower_string() data = {"email": username, "password": password} r = client.post( - f"{settings.API_V1_STR}/users/", headers=normal_user_token_headers, json=data, + f"{settings.API_V1_STR}/users/", + headers=normal_user_token_headers, + json=data, ) assert r.status_code == 400 diff --git a/src/backend/app/tests/conftest.py b/src/backend/app/tests/conftest.py index 722b968d00..0c76fa98c9 100644 --- a/src/backend/app/tests/conftest.py +++ b/src/backend/app/tests/conftest.py @@ -1,4 +1,4 @@ -from typing import Dict, Generator +from collections.abc import Generator import pytest from fastapi.testclient import TestClient @@ -24,12 +24,12 @@ def client() -> Generator: @pytest.fixture(scope="module") -def superuser_token_headers(client: TestClient) -> Dict[str, str]: +def superuser_token_headers(client: TestClient) -> dict[str, str]: return get_superuser_token_headers(client) @pytest.fixture(scope="module") -def normal_user_token_headers(client: TestClient, db: Session) -> Dict[str, str]: +def normal_user_token_headers(client: TestClient, db: Session) -> dict[str, str]: return authentication_token_from_email( client=client, email=settings.EMAIL_TEST_USER, db=db ) diff --git a/src/backend/app/tests/utils/item.py b/src/backend/app/tests/utils/item.py index e28f967078..1ba088a946 100644 --- a/src/backend/app/tests/utils/item.py +++ b/src/backend/app/tests/utils/item.py @@ -1,5 +1,3 @@ -from typing import Optional - from sqlalchemy.orm import Session from app import crud, models @@ -8,7 +6,7 @@ from app.tests.utils.utils import random_lower_string -def create_random_item(db: Session, *, owner_id: Optional[int] = None) -> models.Item: +def create_random_item(db: Session, *, owner_id: int | None = None) -> models.Item: if owner_id is None: user = create_random_user(db) owner_id = user.id diff --git a/src/backend/app/tests/utils/user.py b/src/backend/app/tests/utils/user.py index 2d239fbb3e..e08c0b25fb 100644 --- a/src/backend/app/tests/utils/user.py +++ b/src/backend/app/tests/utils/user.py @@ -1,5 +1,3 @@ -from typing import Dict - from fastapi.testclient import TestClient from sqlalchemy.orm import Session @@ -12,7 +10,7 @@ def user_authentication_headers( *, client: TestClient, email: str, password: str -) -> Dict[str, str]: +) -> dict[str, str]: data = {"username": email, "password": password} r = client.post(f"{settings.API_V1_STR}/login/access-token", data=data) @@ -32,7 +30,7 @@ def create_random_user(db: Session) -> User: def authentication_token_from_email( *, client: TestClient, email: str, db: Session -) -> Dict[str, str]: +) -> dict[str, str]: """ Return a valid token for the user with given email. diff --git a/src/backend/app/tests/utils/utils.py b/src/backend/app/tests/utils/utils.py index 021fc22017..184bac44d9 100644 --- a/src/backend/app/tests/utils/utils.py +++ b/src/backend/app/tests/utils/utils.py @@ -1,6 +1,5 @@ import random import string -from typing import Dict from fastapi.testclient import TestClient @@ -15,7 +14,7 @@ def random_email() -> str: return f"{random_lower_string()}@{random_lower_string()}.com" -def get_superuser_token_headers(client: TestClient) -> Dict[str, str]: +def get_superuser_token_headers(client: TestClient) -> dict[str, str]: login_data = { "username": settings.FIRST_SUPERUSER, "password": settings.FIRST_SUPERUSER_PASSWORD, diff --git a/src/backend/app/utils.py b/src/backend/app/utils.py index b1aba6bc00..ec788efa7c 100644 --- a/src/backend/app/utils.py +++ b/src/backend/app/utils.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta from pathlib import Path -from typing import Any, Dict, Optional +from typing import Any import emails from emails.template import JinjaTemplate @@ -14,8 +14,9 @@ def send_email( email_to: str, subject_template: str = "", html_template: str = "", - environment: Dict[str, Any] = {}, + environment: dict[str, Any] | None = None, ) -> None: + current_environment = environment or {} assert settings.EMAILS_ENABLED, "no provided configuration for email variables" message = emails.Message( subject=JinjaTemplate(subject_template), @@ -29,7 +30,7 @@ def send_email( smtp_options["user"] = settings.SMTP_USER if settings.SMTP_PASSWORD: smtp_options["password"] = settings.SMTP_PASSWORD - response = message.send(to=email_to, render=environment, smtp=smtp_options) + response = message.send(to=email_to, render=current_environment, smtp=smtp_options) logging.info(f"send email result: {response}") @@ -42,7 +43,7 @@ def send_test_email(email_to: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - environment={"project_name": settings.PROJECT_NAME, "email": email_to}, + current_environment={"project_name": settings.PROJECT_NAME, "email": email_to}, ) @@ -57,7 +58,7 @@ def send_reset_password_email(email_to: str, email: str, token: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - environment={ + current_environment={ "project_name": settings.PROJECT_NAME, "username": email, "email": email_to, @@ -77,7 +78,7 @@ def send_new_account_email(email_to: str, username: str, password: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - environment={ + current_environment={ "project_name": settings.PROJECT_NAME, "username": username, "password": password, @@ -93,12 +94,14 @@ def generate_password_reset_token(email: str) -> str: expires = now + delta exp = expires.timestamp() encoded_jwt = jwt.encode( - {"exp": exp, "nbf": now, "sub": email}, settings.SECRET_KEY, algorithm="HS256", + {"exp": exp, "nbf": now, "sub": email}, + settings.SECRET_KEY, + algorithm="HS256", ) return encoded_jwt -def verify_password_reset_token(token: str) -> Optional[str]: +def verify_password_reset_token(token: str) -> str | None: try: decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) return decoded_token["email"] diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 4dde2a4ca7..91abca86b0 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -40,7 +40,7 @@ services: - "5672:5672" - "15672:15672" - "15671:15671" - + flower: ports: - "5555:5555" diff --git a/src/docker-compose.yml b/src/docker-compose.yml index dca06388f9..271dbf8909 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -56,7 +56,7 @@ services: # Redirect a domain without www to www # To enable it remove the previous line and uncomment the next # - traefik.http.middlewares.${STACK_NAME}-www-redirect.redirectregex.replacement=https://www.${DOMAIN}/$${3} - # Middleware to redirect www, to disable it remove the next line + # Middleware to redirect www, to disable it remove the next line - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.middlewares=${STACK_NAME?Variable not set}-www-redirect # Middleware to redirect www, and redirect HTTP to HTTPS # to disable www redirection remove the section: ${STACK_NAME?Variable not set}-www-redirect, @@ -99,7 +99,7 @@ services: # image: rabbitmq:3-management # # You also have to change the flower command - + flower: image: mher/flower:0.9.7 networks: @@ -124,7 +124,7 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls=true - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls.certresolver=le - traefik.http.services.${STACK_NAME?Variable not set}-flower.loadbalancer.server.port=5555 - + backend: image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' depends_on: @@ -146,7 +146,7 @@ services: - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`) - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 - + celeryworker: image: '${DOCKER_IMAGE_CELERYWORKER?Variable not set}:${TAG-latest}' depends_on: @@ -164,7 +164,7 @@ services: dockerfile: celeryworker.dockerfile args: INSTALL_DEV: ${INSTALL_DEV-false} - + frontend: image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' build: @@ -176,7 +176,7 @@ services: - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - + # new-frontend: # image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' # build: diff --git a/src/frontend/.nvmrc b/src/frontend/.nvmrc index 72c7744b30..b460d6f2de 100644 --- a/src/frontend/.nvmrc +++ b/src/frontend/.nvmrc @@ -1 +1 @@ -18.12.1 \ No newline at end of file +18.12.1 diff --git a/src/frontend/babel.config.js b/src/frontend/babel.config.js index 5902d7d136..07b43d2e63 100644 --- a/src/frontend/babel.config.js +++ b/src/frontend/babel.config.js @@ -7,4 +7,4 @@ module.exports = { } ] ] -} \ No newline at end of file +} diff --git a/src/frontend/nginx.conf b/src/frontend/nginx.conf index ed11d3aa19..fd4472f4b5 100644 --- a/src/frontend/nginx.conf +++ b/src/frontend/nginx.conf @@ -1,11 +1,11 @@ server { listen 80; - + location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html =404; } - + include /etc/nginx/extra-conf.d/*.conf; } diff --git a/src/frontend/src/views/main/profile/UserProfileEditPassword.vue b/src/frontend/src/views/main/profile/UserProfileEditPassword.vue index 80e2cc5864..be69392a82 100644 --- a/src/frontend/src/views/main/profile/UserProfileEditPassword.vue +++ b/src/frontend/src/views/main/profile/UserProfileEditPassword.vue @@ -12,7 +12,7 @@
{{userProfile.email}}
- Date: Sun, 25 Feb 2024 21:20:20 +0100 Subject: [PATCH 179/771] =?UTF-8?q?=E2=9C=A8=20Add=20Copier,=20migrate=20f?= =?UTF-8?q?rom=20Cookiecutter,=20in=20a=20way=20that=20supports=20using=20?= =?UTF-8?q?the=20project=20as=20is,=20forking=20or=20cloning=20it=20(#612)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔧 Add first Copier config * 🔧 Add custom copier answers file * 🔨 Add Copier script to update .env after generating/copying * 🙈 Update .gitignores from Copier updates * 🔧 Update .env, restructure in order of relevance * 🔧 Remove Copier config for SMTP port, if necessary, it can be overwritten in .env * ♻️ Refactor Copier files, make them less visible * 🔧 Update Copier config * 🔥 Remove Cookiecutter files --- .gitignore | 2 - cookiecutter.json | 44 ---------- src/.copier/.copier-answers.yml.jinja | 1 + src/.copier/update_dotenv.py | 22 +++++ src/.env | 42 ++++----- src/backend/.gitignore | 3 + src/backend/app/tests/.gitignore | 1 - src/cookiecutter-config-file.yml | 30 ------- src/copier.yml | 119 ++++++++++++++++++++++++++ 9 files changed, 167 insertions(+), 97 deletions(-) delete mode 100644 cookiecutter.json create mode 100644 src/.copier/.copier-answers.yml.jinja create mode 100644 src/.copier/update_dotenv.py delete mode 100755 src/backend/app/tests/.gitignore delete mode 100644 src/cookiecutter-config-file.yml create mode 100644 src/copier.yml diff --git a/.gitignore b/.gitignore index be16fd8a57..722d5e71d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ .vscode -.mypy_cache -poetry.lock diff --git a/cookiecutter.json b/cookiecutter.json deleted file mode 100644 index fc0e6fab00..0000000000 --- a/cookiecutter.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "project_name": "Base Project", - "project_slug": "{{ cookiecutter.project_name|lower|replace(' ', '-') }}", - "domain_main": "{{cookiecutter.project_slug}}.com", - "domain_staging": "stag.{{cookiecutter.domain_main}}", - - "docker_swarm_stack_name_main": "{{cookiecutter.domain_main|replace('.', '-')}}", - "docker_swarm_stack_name_staging": "{{cookiecutter.domain_staging|replace('.', '-')}}", - - "secret_key": "changethis", - "first_superuser": "admin@{{cookiecutter.domain_main}}", - "first_superuser_password": "changethis", - "backend_cors_origins": "[\"http://localhost\", \"http://localhost:4200\", \"http://localhost:3000\", \"http://localhost:8080\", \"https://localhost\", \"https://localhost:4200\", \"https://localhost:3000\", \"https://localhost:8080\", \"http://dev.{{cookiecutter.domain_main}}\", \"https://{{cookiecutter.domain_staging}}\", \"https://{{cookiecutter.domain_main}}\", \"http://local.dockertoolbox.tiangolo.com\", \"http://localhost.tiangolo.com\"]", - "smtp_port": "587", - "smtp_host": "", - "smtp_user": "", - "smtp_password": "", - "smtp_emails_from_email": "info@{{cookiecutter.domain_main}}", - - "postgres_password": "changethis", - "pgadmin_default_user": "{{cookiecutter.first_superuser}}", - "pgadmin_default_user_password": "{{cookiecutter.first_superuser_password}}", - - "traefik_constraint_tag": "{{cookiecutter.domain_main}}", - "traefik_constraint_tag_staging": "{{cookiecutter.domain_staging}}", - "traefik_public_constraint_tag": "traefik-public", - - "flower_auth": "admin:{{cookiecutter.first_superuser_password}}", - - "sentry_dsn": "", - - "docker_image_prefix": "", - - "docker_image_backend": "{{cookiecutter.docker_image_prefix}}backend", - "docker_image_celeryworker": "{{cookiecutter.docker_image_prefix}}celeryworker", - "docker_image_frontend": "{{cookiecutter.docker_image_prefix}}frontend", - - "_copy_without_render": [ - "frontend/src/**/*.html", - "frontend/src/**/*.vue", - "frontend/node_modules/*", - "backend/app/app/email-templates/**" - ] -} diff --git a/src/.copier/.copier-answers.yml.jinja b/src/.copier/.copier-answers.yml.jinja new file mode 100644 index 0000000000..0028a2398a --- /dev/null +++ b/src/.copier/.copier-answers.yml.jinja @@ -0,0 +1 @@ +{{ _copier_answers|to_json -}} diff --git a/src/.copier/update_dotenv.py b/src/.copier/update_dotenv.py new file mode 100644 index 0000000000..1eab4c8ffe --- /dev/null +++ b/src/.copier/update_dotenv.py @@ -0,0 +1,22 @@ +from pathlib import Path +import json + +# Update the .env file with the answers from the .copier-answers.yml file +# without using Jinja2 templates in the .env file, this way the code works as is +# without needing Copier, but if Copier is used, the .env file will be updated +root_path = Path(__file__).parent.parent +answers_path = Path(__file__).parent / ".copier-answers.yml" +answers = json.loads(answers_path.read_text()) +env_path = root_path / ".env" +env_content = env_path.read_text() +lines = [] +for line in env_content.splitlines(): + for key, value in answers.items(): + upper_key = key.upper() + if line.startswith(f"{upper_key}="): + new_line = line.replace(line, f"{upper_key}={value}") + lines.append(new_line) + break + else: + lines.append(line) +env_path.write_text("\n".join(lines)) diff --git a/src/.env b/src/.env index 78bf4c65cd..2cf8e643e9 100644 --- a/src/.env +++ b/src/.env @@ -2,45 +2,47 @@ DOMAIN=localhost # DOMAIN=localhost.tiangolo.com -STACK_NAME=full-stack-fastapi-postgresql - -TRAEFIK_PUBLIC_NETWORK=traefik-public -TRAEFIK_TAG=traefik -TRAEFIK_PUBLIC_TAG=traefik-public +PROJECT_NAME="FastAPI Project" -# Configure these with your own Docker registry images -DOCKER_IMAGE_BACKEND=backend -DOCKER_IMAGE_CELERYWORKER=celery -DOCKER_IMAGE_FRONTEND=frontend -DOCKER_IMAGE_NEW_FRONTEND=new-frontend +STACK_NAME=fastapi-project # Backend BACKEND_CORS_ORIGINS="[\"http://localhost\", \"http://localhost:4200\", \"http://localhost:3000\", \"http://localhost:8080\", \"https://localhost\", \"https://localhost:4200\", \"https://localhost:3000\", \"https://localhost:8080\", \"http://local.dockertoolbox.tiangolo.com\", \"http://localhost.tiangolo.com\"]" -PROJECT_NAME="FastAPI Project" SECRET_KEY=changethis FIRST_SUPERUSER=admin@example.com FIRST_SUPERUSER_PASSWORD=changethis -SMTP_TLS=True -SMTP_PORT=587 SMTP_HOST= SMTP_USER= SMTP_PASSWORD= EMAILS_FROM_EMAIL=info@example.com +SMTP_TLS=True +SMTP_PORT=587 USERS_OPEN_REGISTRATION=False -SENTRY_DSN= - -# Flower -FLOWER_BASIC_AUTH= - # Postgres POSTGRES_SERVER=db POSTGRES_USER=postgres -POSTGRES_PASSWORD=changethis POSTGRES_DB=app +POSTGRES_PASSWORD=changethis # PgAdmin -PGADMIN_LISTEN_PORT=5050 PGADMIN_DEFAULT_EMAIL=admin@example.com PGADMIN_DEFAULT_PASSWORD=changethis +PGADMIN_LISTEN_PORT=5050 + +SENTRY_DSN= + +# Flower +FLOWER_BASIC_AUTH= + +# Traefik +TRAEFIK_PUBLIC_NETWORK=traefik-public +TRAEFIK_TAG=traefik +TRAEFIK_PUBLIC_TAG=traefik-public + +# Configure these with your own Docker registry images +DOCKER_IMAGE_BACKEND=backend +DOCKER_IMAGE_CELERYWORKER=celery +DOCKER_IMAGE_FRONTEND=frontend +DOCKER_IMAGE_NEW_FRONTEND=new-frontend diff --git a/src/backend/.gitignore b/src/backend/.gitignore index 8078a84461..9660d71665 100644 --- a/src/backend/.gitignore +++ b/src/backend/.gitignore @@ -4,3 +4,6 @@ app.egg-info .mypy_cache .coverage htmlcov +poetry.lock +.cache +.venv diff --git a/src/backend/app/tests/.gitignore b/src/backend/app/tests/.gitignore deleted file mode 100755 index 16d3c4dbbf..0000000000 --- a/src/backend/app/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.cache diff --git a/src/cookiecutter-config-file.yml b/src/cookiecutter-config-file.yml deleted file mode 100644 index b0e7dd2f77..0000000000 --- a/src/cookiecutter-config-file.yml +++ /dev/null @@ -1,30 +0,0 @@ -default_context: - project_name: '{{ cookiecutter.project_name }}' - project_slug: '{{ cookiecutter.project_slug }}' - domain_main: '{{ cookiecutter.domain_main }}' - domain_staging: '{{ cookiecutter.domain_staging }}' - docker_swarm_stack_name_main: '{{ cookiecutter.docker_swarm_stack_name_main }}' - docker_swarm_stack_name_staging: '{{ cookiecutter.docker_swarm_stack_name_staging }}' - secret_key: '{{ cookiecutter.secret_key }}' - first_superuser: '{{ cookiecutter.first_superuser }}' - first_superuser_password: '{{ cookiecutter.first_superuser_password }}' - backend_cors_origins: '{{ cookiecutter.backend_cors_origins }}' - smtp_port: '{{ cookiecutter.smtp_port }}' - smtp_host: '{{ cookiecutter.smtp_host }}' - smtp_user: '{{ cookiecutter.smtp_user }}' - smtp_password: '{{ cookiecutter.smtp_password }}' - smtp_emails_from_email: '{{ cookiecutter.smtp_emails_from_email }}' - postgres_password: '{{ cookiecutter.postgres_password }}' - pgadmin_default_user: '{{ cookiecutter.pgadmin_default_user }}' - pgadmin_default_user_password: '{{ cookiecutter.pgadmin_default_user_password }}' - traefik_constraint_tag: '{{ cookiecutter.traefik_constraint_tag }}' - traefik_constraint_tag_staging: '{{ cookiecutter.traefik_constraint_tag_staging }}' - traefik_public_constraint_tag: '{{ cookiecutter.traefik_public_constraint_tag }}' - flower_auth: '{{ cookiecutter.flower_auth }}' - sentry_dsn: '{{ cookiecutter.sentry_dsn }}' - docker_image_prefix: '{{ cookiecutter.docker_image_prefix }}' - docker_image_backend: '{{ cookiecutter.docker_image_backend }}' - docker_image_celeryworker: '{{ cookiecutter.docker_image_celeryworker }}' - docker_image_frontend: '{{ cookiecutter.docker_image_frontend }}' - _copy_without_render: [frontend/src/**/*.html, frontend/src/**/*.vue, frontend/node_modules/*, backend/app/app/email-templates/**] - _template: ./ diff --git a/src/copier.yml b/src/copier.yml new file mode 100644 index 0000000000..743b5ff523 --- /dev/null +++ b/src/copier.yml @@ -0,0 +1,119 @@ +domain: + type: str + help: | + Which domain name to use for the project, by default, + localhost, but you should change it later (in .env) + default: localhost + +project_name: + type: str + help: The name of the project, shown to API users (in .env) + default: FastAPI Project + +stack_name: + type: str + help: The name of the stack used for Docker Compose labels (no spaces) (in .env) + default: fastapi-project + +secret_key: + type: str + help: | + 'The secret key for the project, used for security, + stored in .env, you can generate one with: + python -c "import secrets; print(secrets.token_urlsafe(32))"' + default: changethis + +first_superuser: + type: str + help: The email of the first superuser (in .env) + default: admin@example.com + +first_superuser_password: + type: str + help: The password of the first superuser (in .env) + default: changethis + +smtp_host: + type: str + help: The SMTP server host to send emails, you can set it later in .env + default: "" + +smtp_user: + type: str + help: The SMTP server user to send emails, you can set it later in .env + default: "" + +smtp_password: + type: str + help: The SMTP server password to send emails, you can set it later in .env + default: "" + +emails_from_email: + type: str + help: The email account to send emails from, you can set it later in .env + default: info@example.com + +postgres_password: + type: str + help: | + 'The password for the PostgreSQL database, stored in .env, + you can generate one with: + python -c "import secrets; print(secrets.token_urlsafe(32))"' + default: changethis + +pgadmin_default_email: + type: str + help: The default user email for pgAdmin, you can set it later in .env + default: admin@example.com + +pgadmin_default_password: + type: str + help: The default user password for pgAdmin, stored in .env + default: changethis + +sentry_dsn: + type: str + help: The DSN for Sentry, if you are using it, you can set it later in .env + default: "" + +_exclude: + # Global + - .vscode + - .mypy_cache + - poetry.lock + # Python + - __pycache__ + - app.egg-info + - "*.pyc" + - .mypy_cache + - .coverage + - htmlcov + - poetry.lock + - .cache + - .venv + # Frontend + # Logs + - logs + - "*.log" + - npm-debug.log* + - yarn-debug.log* + - yarn-error.log* + - pnpm-debug.log* + - lerna-debug.log* + - node_modules + - dist + - dist-ssr + - "*.local" + # Editor directories and files + - .idea + - .DS_Store + - "*.suo" + - "*.ntvs*" + - "*.njsproj" + - "*.sln" + - "*.sw?" + +_answers_file: .copier/.copier-answers.yml + +_tasks: + - "python .copier/update_dotenv.py" From 49af524ad78d1b7468b8903f9b7991e41cded4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Feb 2024 21:51:56 +0100 Subject: [PATCH 180/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20internal=20READ?= =?UTF-8?q?ME=20and=20referred=20files=20(#613)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.gitlab-ci.yml | 74 ----- src/README.md | 517 ++++------------------------------ src/backend/app/db/init_db.py | 9 +- 3 files changed, 54 insertions(+), 546 deletions(-) delete mode 100644 src/.gitlab-ci.yml diff --git a/src/.gitlab-ci.yml b/src/.gitlab-ci.yml deleted file mode 100644 index cd730c1b80..0000000000 --- a/src/.gitlab-ci.yml +++ /dev/null @@ -1,74 +0,0 @@ -image: tiangolo/docker-with-compose - -before_script: - - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY - - pip install docker-auto-labels - -stages: - - test - - build - - deploy - -tests: - stage: test - script: - - sh ./scripts/test.sh - tags: - - build - - test - -build-stag: - stage: build - script: - - TAG=stag FRONTEND_ENV=staging sh ./scripts/build-push.sh - only: - - master - tags: - - build - - test - -build-prod: - stage: build - script: - - TAG=prod FRONTEND_ENV=production sh ./scripts/build-push.sh - only: - - production - tags: - - build - - test - -deploy-stag: - stage: deploy - script: - - > - DOMAIN={{cookiecutter.domain_staging}} - TRAEFIK_TAG={{cookiecutter.traefik_constraint_tag_staging}} - STACK_NAME={{cookiecutter.docker_swarm_stack_name_staging}} - TAG=stag - sh ./scripts/deploy.sh - environment: - name: staging - url: https://{{cookiecutter.domain_staging}} - only: - - master - tags: - - swarm - - stag - -deploy-prod: - stage: deploy - script: - - > - DOMAIN={{cookiecutter.domain_main}} - TRAEFIK_TAG={{cookiecutter.traefik_constraint_tag}} - STACK_NAME={{cookiecutter.docker_swarm_stack_name_main}} - TAG=prod - sh ./scripts/deploy.sh - environment: - name: production - url: https://{{cookiecutter.domain_main}} - only: - - production - tags: - - swarm - - prod diff --git a/src/README.md b/src/README.md index 5594dd546d..736ce1e0e4 100644 --- a/src/README.md +++ b/src/README.md @@ -1,9 +1,8 @@ -# {{cookiecutter.project_name}} +# FastAPI Project ## Backend Requirements * [Docker](https://www.docker.com/). -* [Docker Compose](https://docs.docker.com/compose/install/). * [Poetry](https://python-poetry.org/) for Python package and environment management. ## Frontend Requirements @@ -15,7 +14,7 @@ * Start the stack with Docker Compose: ```bash -docker-compose up -d +docker compose up -d ``` * Now you can open your browser and interact with these URLs: @@ -26,8 +25,6 @@ Backend, JSON based web API based on OpenAPI: http://localhost/api/ Automatic interactive documentation with Swagger UI (from the OpenAPI backend): http://localhost/docs -Alternative automatic documentation with ReDoc (from the OpenAPI backend): http://localhost/redoc - PGAdmin, PostgreSQL web administration: http://localhost:5050 Flower, administration of Celery tasks: http://localhost:5555 @@ -39,16 +36,16 @@ Traefik UI, to see how the routes are being handled by the proxy: http://localho To check the logs, run: ```bash -docker-compose logs +docker compose logs ``` To check the logs of a specific service, add the name of the service, e.g.: ```bash -docker-compose logs backend +docker compose logs backend ``` -If your Docker is not running in `localhost` (the URLs above wouldn't work) check the sections below on **Development with Docker Toolbox** and **Development with a custom IP**. +If your Docker is not running in `localhost` (the URLs above wouldn't work) you would need to use the IP or domain where your Docker is running. ## Backend local development, additional details @@ -56,7 +53,7 @@ If your Docker is not running in `localhost` (the URLs above wouldn't work) chec By default, the dependencies are managed with [Poetry](https://python-poetry.org/), go there and install it. -From `./backend/app/` you can install all the dependencies with: +From `./backend/` you can install all the dependencies with: ```console $ poetry install @@ -68,17 +65,15 @@ Then you can start a shell session with the new environment with: $ poetry shell ``` -Next, open your editor at `./backend/app/` (instead of the project root: `./`), so that you see an `./app/` directory with your code inside. That way, your editor will be able to find all the imports, etc. Make sure your editor uses the environment you just created with Poetry. - -Modify or add SQLAlchemy models in `./backend/app/app/models/`, Pydantic schemas in `./backend/app/app/schemas/`, API endpoints in `./backend/app/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/app/crud/`. The easiest might be to copy the ones for Items (models, endpoints, and CRUD utils) and update them to your needs. +Next, open your editor at `./backend/` (instead of the project root: `./`), so that you see an `./app/` directory with your code inside. That way, your editor will be able to find all the imports, etc. Make sure your editor uses the environment you just created with Poetry. -Add and modify tasks to the Celery worker in `./backend/app/app/worker.py`. +Modify or add SQLModel models for data and SQL tables in `./backend/app/models.py`, API endpoints in `./backend/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/crud.py`. -If you need to install any additional package to the worker, add it to the file `./backend/app/celeryworker.dockerfile`. +Add and modify tasks to the Celery worker in `./backend/app/worker.py`. ### Docker Compose Override -During development, you can change Docker Compose settings that will only affect the local development environment, in the file `docker-compose.override.yml`. +During development, you can change Docker Compose settings that will only affect the local development environment in the file `docker-compose.override.yml`. The changes to that file only affect the local development environment, not the production environment. So, you can add "temporary" changes that help the development workflow. @@ -87,21 +82,21 @@ For example, the directory with the backend code is mounted as a Docker "host vo There is also a command override that runs `/start-reload.sh` (included in the base image) instead of the default `/start.sh` (also included in the base image). It starts a single server process (instead of multiple, as would be for production) and reloads the process whenever the code changes. Have in mind that if you have a syntax error and save the Python file, it will break and exit, and the container will stop. After that, you can restart the container by fixing the error and running again: ```console -$ docker-compose up -d +$ docker compose up -d ``` -There is also a commented out `command` override, you can uncomment it and comment the default one. It makes the backend container run a process that does "nothing", but keeps the container alive. That allows you to get inside your running container and execute commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes, or start a Jupyter Notebook session. +There is also a commented out `command` override, you can uncomment it and comment the default one. It makes the backend container run a process that does "nothing", but keeps the container alive. That allows you to get inside your running container and execute commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes. To get inside the container with a `bash` session you can start the stack with: ```console -$ docker-compose up -d +$ docker compose up -d ``` and then `exec` inside the running container: ```console -$ docker-compose exec backend bash +$ docker compose exec backend bash ``` You should see an output like: @@ -110,7 +105,7 @@ You should see an output like: root@7f2607af31c3:/app# ``` -that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory. +that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory, this directory has another directory called "app" inside, that's where your code lives inside the container: `/app/app`. There you can use the script `/start-reload.sh` to run the debug live reloading server. You can run that script from inside the container with: @@ -135,35 +130,19 @@ Nevertheless, if it doesn't detect a change but a syntax error, it will just sto To test the backend run: ```console -$ DOMAIN=backend sh ./scripts/test.sh +$ bash ./scripts/test.sh ``` -The file `./scripts/test.sh` has the commands to generate a testing `docker-stack.yml` file, start the stack and test it. - -The tests run with Pytest, modify and add tests to `./backend/app/app/tests/`. - -If you use GitLab CI the tests will run automatically. +The tests run with Pytest, modify and add tests to `./backend/app/tests/`. -#### Local tests - -Start the stack with this command: - -```Bash -DOMAIN=backend sh ./scripts/test-local.sh -``` -The `./backend/app` directory is mounted as a "host volume" inside the docker container (set in the file `docker-compose.dev.volumes.yml`). -You can rerun the test on live code: - -```Bash -docker-compose exec backend /app/tests-start.sh -``` +If you use GitHub Actions the tests will run automatically. #### Test running stack If your stack is already up and you just want to run the tests, you can use: ```bash -docker-compose exec backend /app/tests-start.sh +docker compose exec backend /app/tests-start.sh ``` That `/app/tests-start.sh` script just calls `pytest` after making sure that the rest of the stack is running. If you need to pass extra arguments to `pytest`, you can pass them to that command and they will be forwarded. @@ -171,7 +150,7 @@ That `/app/tests-start.sh` script just calls `pytest` after making sure that the For example, to stop on first error: ```bash -docker-compose exec backend bash /app/tests-start.sh -x +docker compose exec backend bash /app/tests-start.sh -x ``` #### Test Coverage @@ -187,50 +166,9 @@ DOMAIN=backend sh ./scripts/test-local.sh --cov-report=html To run the tests in a running stack with coverage HTML reports: ```bash -docker-compose exec backend bash /app/tests-start.sh --cov-report=html -``` - -### Live development with Python Jupyter Notebooks - -If you know about Python [Jupyter Notebooks](http://jupyter.org/), you can take advantage of them during local development. - -The `docker-compose.override.yml` file sends a variable `env` with a value `dev` to the build process of the Docker image (during local development) and the `Dockerfile` has steps to then install and configure Jupyter inside your Docker container. - -So, you can enter into the running Docker container: - -```bash -docker-compose exec backend bash -``` - -And use the environment variable `$JUPYTER` to run a Jupyter Notebook with everything configured to listen on the public port (so that you can use it from your browser). - -It will output something like: - -```console -root@73e0ec1f1ae6:/app# $JUPYTER -[I 12:02:09.975 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret -[I 12:02:10.317 NotebookApp] Serving notebooks from local directory: /app -[I 12:02:10.317 NotebookApp] The Jupyter Notebook is running at: -[I 12:02:10.317 NotebookApp] http://(73e0ec1f1ae6 or 127.0.0.1):8888/?token=f20939a41524d021fbfc62b31be8ea4dd9232913476f4397 -[I 12:02:10.317 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). -[W 12:02:10.317 NotebookApp] No web browser found: could not locate runnable browser. -[C 12:02:10.317 NotebookApp] - - Copy/paste this URL into your browser when you connect for the first time, - to login with a token: - http://(73e0ec1f1ae6 or 127.0.0.1):8888/?token=f20939a41524d021fbfc62b31be8ea4dd9232913476f4397 -``` - -you can copy that URL and modify the "host" to be `localhost` or the domain you are using for development (e.g. `local.dockertoolbox.tiangolo.com`), in the case above, it would be, e.g.: - -``` -http://localhost:8888/token=f20939a41524d021fbfc62b31be8ea4dd9232913476f4397 +docker compose exec backend bash /app/tests-start.sh --cov-report=html ``` - and then open it in your browser. - -You will have a full Jupyter Notebook running inside your container that has direct access to your database by the container name (`db`), etc. So, you can just run sections of your backend code directly, for example with [VS Code Python Jupyter Interactive Window](https://code.visualstudio.com/docs/python/jupyter-support-py) or [Hydrogen](https://github.com/nteract/hydrogen). - ### Migrations As during local development your app directory is mounted as a volume inside the container, you can also run the migrations with `alembic` commands inside the container and the migration code will be in your app directory (instead of being only inside the container). So you can add it to your git repository. @@ -240,10 +178,10 @@ Make sure you create a "revision" of your models and that you "upgrade" your dat * Start an interactive session in the backend container: ```console -$ docker-compose exec backend bash +$ docker compose exec backend bash ``` -* If you created a new model in `./backend/app/app/models/`, make sure to import it in `./backend/app/app/db/base.py`, that Python module (`base.py`) that imports all the models will be used by Alembic. +* Alembic is already configured to import your SQLModel models from `./backend/app/models.py`. * After changing a model (for example, adding a column), inside the container, create a revision, e.g.: @@ -259,10 +197,10 @@ $ alembic revision --autogenerate -m "Add column last_name to User model" $ alembic upgrade head ``` -If you don't want to use migrations at all, uncomment the line in the file at `./backend/app/app/db/init_db.py` with: +If you don't want to use migrations at all, uncomment the lines in the file at `./backend/app/db/init_db.py` that end in: ```python -Base.metadata.create_all(bind=engine) +SQLModel.metadata.create_all(engine) ``` and comment the line in the file `prestart.sh` that contains: @@ -273,22 +211,6 @@ $ alembic upgrade head If you don't want to start with the default models and want to remove them / modify them, from the beginning, without having any previous revision, you can remove the revision files (`.py` Python files) under `./backend/app/alembic/versions/`. And then create a first migration as described above. -### Development with Docker Toolbox - -If you are using **Docker Toolbox** in Windows or macOS instead of **Docker for Windows** or **Docker for Mac**, Docker will be running in a VirtualBox Virtual Machine, and it will have a local IP different than `127.0.0.1`, which is the IP address for `localhost` in your machine. - -The address of your Docker Toolbox virtual machine would probably be `192.168.99.100` (that is the default). - -As this is a common case, the domain `local.dockertoolbox.tiangolo.com` points to that (private) IP, just to help with development (actually `dockertoolbox.tiangolo.com` and all its subdomains point to that IP). That way, you can start the stack in Docker Toolbox, and use that domain for development. You will be able to open that URL in Chrome and it will communicate with your local Docker Toolbox directly as if it was a cloud server, including CORS (Cross Origin Resource Sharing). - -If you used the default CORS enabled domains while generating the project, `local.dockertoolbox.tiangolo.com` was configured to be allowed. If you didn't, you will need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. - -To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `local.dockertoolbox.tiangolo.com`. - -After performing those steps you should be able to open: http://local.dockertoolbox.tiangolo.com and it will be server by your stack in your Docker Toolbox virtual machine. - -Check all the corresponding available URLs in the section at the end. - ### Development in `localhost` with a custom domain You might want to use something different than `localhost` as the domain. For example, if you are having problems with cookies that need a subdomain, and Chrome is not allowing you to use `localhost`. @@ -299,46 +221,46 @@ If you used the default CORS enabled domains while generating the project, `loca To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `localhost.tiangolo.com`. -After performing those steps you should be able to open: http://localhost.tiangolo.com and it will be server by your stack in `localhost`. +After performing those steps you should be able to open: http://localhost.tiangolo.com and it will be served by your stack in `localhost`. Check all the corresponding available URLs in the section at the end. ### Development with a custom IP -If you are running Docker in an IP address different than `127.0.0.1` (`localhost`) and `192.168.99.100` (the default of Docker Toolbox), you will need to perform some additional steps. That will be the case if you are running a custom Virtual Machine, a secondary Docker Toolbox or your Docker is located in a different machine in your network. +If you are running Docker in an IP address different than `127.0.0.1` (`localhost`), you will need to perform some additional steps. That will be the case if you are running a custom Virtual Machine or your Docker is located in a different machine in your network. -In that case, you will need to use a fake local domain (`dev.{{cookiecutter.domain_main}}`) and make your computer think that the domain is is served by the custom IP (e.g. `192.168.99.150`). +In that case, you will need to use a fake local domain (`dev.example.com`) and make your computer think that the domain is is served by the custom IP (e.g. `192.168.99.150`). -If you used the default CORS enabled domains, `dev.{{cookiecutter.domain_main}}` was configured to be allowed. If you want a custom one, you need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. +If you have a custom domain like that, you need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. * Open your `hosts` file with administrative privileges using a text editor: * **Note for Windows**: If you are in Windows, open the main Windows menu, search for "notepad", right click on it, and select the option "open as Administrator" or similar. Then click the "File" menu, "Open file", go to the directory `c:\Windows\System32\Drivers\etc\`, select the option to show "All files" instead of only "Text (.txt) files", and open the `hosts` file. * **Note for Mac and Linux**: Your `hosts` file is probably located at `/etc/hosts`, you can edit it in a terminal running `sudo nano /etc/hosts`. -* Additional to the contents it might have, add a new line with the custom IP (e.g. `192.168.99.150`) a space character, and your fake local domain: `dev.{{cookiecutter.domain_main}}`. +* Additional to the contents it might have, add a new line with the custom IP (e.g. `192.168.99.150`) a space character, and your fake local domain: `dev.example.com`. The new line might look like: ``` -192.168.99.100 dev.{{cookiecutter.domain_main}} +192.168.99.150 dev.example.com ``` * Save the file. * **Note for Windows**: Make sure you save the file as "All files", without an extension of `.txt`. By default, Windows tries to add the extension. Make sure the file is saved as is, without extension. -...that will make your computer think that the fake local domain is served by that custom IP, and when you open that URL in your browser, it will talk directly to your locally running server when it is asked to go to `dev.{{cookiecutter.domain_main}}` and think that it is a remote server while it is actually running in your computer. +...that will make your computer think that the fake local domain is served by that custom IP, and when you open that URL in your browser, it will talk directly to your locally running server when it is asked to go to `dev.example.com` and think that it is a remote server while it is actually running in your computer. -To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `dev.{{cookiecutter.domain_main}}`. +To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `dev.example.com`. -After performing those steps you should be able to open: http://dev.{{cookiecutter.domain_main}} and it will be server by your stack in `localhost`. +After performing those steps you should be able to open: http://dev.example.com and it will be server by your stack in `192.168.99.150`. Check all the corresponding available URLs in the section at the end. ### Change the development "domain" -If you need to use your local stack with a different domain than `localhost`, you need to make sure the domain you use points to the IP where your stack is set up. See the different ways to achieve that in the sections above (i.e. using Docker Toolbox with `local.dockertoolbox.tiangolo.com`, using `localhost.tiangolo.com` or using `dev.{{cookiecutter.domain_main}}`). +If you need to use your local stack with a different domain than `localhost`, you need to make sure the domain you use points to the IP where your stack is set up. -To simplify your Docker Compose setup, for example, so that the API docs (Swagger UI) knows where is your API, you should let it know you are using that domain for development. You will need to edit 1 line in 2 files. +To simplify your Docker Compose setup, for example, so that the API docs (Swagger UI) knows where is your API, you should let it know you are using that domain for development. * Open the file located at `./.env`. It would have a line like: @@ -354,24 +276,10 @@ DOMAIN=localhost.tiangolo.com That variable will be used by the Docker Compose files. -* Now open the file located at `./frontend/.env`. It would have a line like: - -``` -VUE_APP_DOMAIN_DEV=localhost -``` - -* Change that line to the domain you are going to use, e.g.: - -``` -VUE_APP_DOMAIN_DEV=localhost.tiangolo.com -``` - -That variable will make your frontend communicate with that domain when interacting with your backend API, when the other variable `VUE_APP_ENV` is set to `development`. - -After changing the two lines, you can re-start your stack with: +After that, you can restart your stack with: ```bash -docker-compose up -d +docker compose up -d ``` and check all the corresponding available URLs in the section at the end. @@ -383,33 +291,15 @@ and check all the corresponding available URLs in the section at the end. ```bash cd frontend npm install -npm run serve +npm run dev ``` -Then open your browser at http://localhost:8080 +Then open your browser at http://localhost:5173/. Notice that this live server is not running inside Docker, it is for local development, and that is the recommended workflow. Once you are happy with your frontend, you can build the frontend Docker image and start it, to test it in a production-like environment. But compiling the image at every change will not be as productive as running the local development server with live reload. Check the file `package.json` to see other available options. -If you have Vue CLI installed, you can also run `vue ui` to control, configure, serve, and analyze your application using a nice local web user interface. - -If you are only developing the frontend (e.g. other team members are developing the backend) and there is a staging environment already deployed, you can make your local development code use that staging API instead of a full local Docker Compose stack. - -To do that, modify the file `./frontend/.env`, there's a section with: - -``` -VUE_APP_ENV=development -# VUE_APP_ENV=staging -``` - -* Switch the comment, to: - -``` -# VUE_APP_ENV=development -VUE_APP_ENV=staging -``` - ### Removing the frontend If you are developing an API-only app and want to remove the frontend, you can do it easily: @@ -425,14 +315,13 @@ Done, you have a frontend-less (api-only) app. 🔥 🚀 If you want, you can also remove the `FRONTEND` environment variables from: * `.env` -* `.gitlab-ci.yml` * `./scripts/*.sh` But it would be only to clean them up, leaving them won't really have any effect either way. ## Deployment -You can deploy the stack to a Docker Swarm mode cluster with a main Traefik proxy, set up using the ideas from DockerSwarm.rocks, to get automatic HTTPS certificates, etc. +You can deploy the using Docker Compose with a main Traefik proxy outside handling communication to the outside world and HTTPS certificates. And you can use CI (continuous integration) systems to do it automatically. @@ -440,7 +329,7 @@ But you have to configure a couple things first. ### Traefik network -This stack expects the public Traefik network to be named `traefik-public`, just as in the tutorials in DockerSwarm.rocks. +This stack expects the public Traefik network to be named `traefik-public`. If you need to use a different Traefik public network name, update it in the `docker-compose.yml` files, in the section: @@ -456,234 +345,19 @@ Change `traefik-public` to the name of the used Traefik network. And then update TRAEFIK_PUBLIC_NETWORK=traefik-public ``` -### Persisting Docker named volumes - -You need to make sure that each service (Docker container) that uses a volume is always deployed to the same Docker "node" in the cluster, that way it will preserve the data. Otherwise, it could be deployed to a different node each time, and each time the volume would be created in that new node before starting the service. As a result, it would look like your service was starting from scratch every time, losing all the previous data. - -That's specially important for a service running a database. But the same problem would apply if you were saving files in your main backend service (for example, if those files were uploaded by your users, or if they were created by your system). - -To solve that, you can put constraints in the services that use one or more data volumes (like databases) to make them be deployed to a Docker node with a specific label. And of course, you need to have that label assigned to one (only one) of your nodes. - -#### Adding services with volumes - -For each service that uses a volume (databases, services with uploaded files, etc) you should have a label constraint in your `docker-compose.yml` file. - -To make sure that your labels are unique per volume per stack (for example, that they are not the same for `prod` and `stag`) you should prefix them with the name of your stack and then use the same name of the volume. - -Then you need to have those constraints in your `docker-compose.yml` file for the services that need to be fixed with each volume. - -To be able to use different environments, like `prod` and `stag`, you should pass the name of the stack as an environment variable. Like: - -```bash -STACK_NAME={{cookiecutter.docker_swarm_stack_name_staging}} sh ./scripts/deploy.sh -``` - -To use and expand that environment variable inside the `docker-compose.yml` files you can add the constraints to the services like: - -```yaml -version: '3' -services: - db: - volumes: - - 'app-db-data:/var/lib/postgresql/data/pgdata' - deploy: - placement: - constraints: - - node.labels.${STACK_NAME?Variable not set}.app-db-data == true -``` - -note the `${STACK_NAME?Variable not set}`. In the script `./scripts/deploy.sh`, the `docker-compose.yml` would be converted, and saved to a file `docker-stack.yml` containing: - -```yaml -version: '3' -services: - db: - volumes: - - 'app-db-data:/var/lib/postgresql/data/pgdata' - deploy: - placement: - constraints: - - node.labels.{{cookiecutter.docker_swarm_stack_name_main}}.app-db-data == true -``` - -**Note**: The `${STACK_NAME?Variable not set}` means "use the environment variable `STACK_NAME`, but if it is not set, show an error `Variable not set`". - -If you add more volumes to your stack, you need to make sure you add the corresponding constraints to the services that use that named volume. - -Then you have to create those labels in some nodes in your Docker Swarm mode cluster. You can use `docker-auto-labels` to do it automatically. - -#### `docker-auto-labels` - -You can use [`docker-auto-labels`](https://github.com/tiangolo/docker-auto-labels) to automatically read the placement constraint labels in your Docker stack (Docker Compose file) and assign them to a random Docker node in your Swarm mode cluster if those labels don't exist yet. - -To do that, you can install `docker-auto-labels`: - -```bash -pip install docker-auto-labels -``` - -And then run it passing your `docker-stack.yml` file as a parameter: - -```bash -docker-auto-labels docker-stack.yml -``` - -You can run that command every time you deploy, right before deploying, as it doesn't modify anything if the required labels already exist. - -#### (Optionally) adding labels manually - -If you don't want to use `docker-auto-labels` or for any reason you want to manually assign the constraint labels to specific nodes in your Docker Swarm mode cluster, you can do the following: - -* First, connect via SSH to your Docker Swarm mode cluster. - -* Then check the available nodes with: - -```console -$ docker node ls - - -// you would see an output like: - -ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS -nfa3d4df2df34as2fd34230rm * dog.example.com Ready Active Reachable -2c2sd2342asdfasd42342304e cat.example.com Ready Active Leader -c4sdf2342asdfasd4234234ii snake.example.com Ready Active Reachable -``` - -then chose a node from the list. For example, `dog.example.com`. - -* Add the label to that node. Use as label the name of the stack you are deploying followed by a dot (`.`) followed by the named volume, and as value, just `true`, e.g.: - -```bash -docker node update --label-add {{cookiecutter.docker_swarm_stack_name_main}}.app-db-data=true dog.example.com -``` - -* Then you need to do the same for each stack version you have. For example, for staging you could do: - -```bash -docker node update --label-add {{cookiecutter.docker_swarm_stack_name_staging}}.app-db-data=true cat.example.com -``` - -### Deploy to a Docker Swarm mode cluster - -There are 3 steps: - -1. **Build** your app images -2. Optionally, **push** your custom images to a Docker Registry -3. **Deploy** your stack - ---- - -Here are the steps in detail: - -1. **Build your app images** - -* Set these environment variables, right before the next command: - * `TAG=prod` - * `FRONTEND_ENV=production` -* Use the provided `scripts/build.sh` file with those environment variables: - -```bash -TAG=prod FRONTEND_ENV=production bash ./scripts/build.sh -``` - -2. **Optionally, push your images to a Docker Registry** - -**Note**: if the deployment Docker Swarm mode "cluster" has more than one server, you will have to push the images to a registry or build the images in each server, so that when each of the servers in your cluster tries to start the containers it can get the Docker images for them, pulling them from a Docker Registry or because it has them already built locally. - -If you are using a registry and pushing your images, you can omit running the previous script and instead using this one, in a single shot. - -* Set these environment variables: - * `TAG=prod` - * `FRONTEND_ENV=production` -* Use the provided `scripts/build-push.sh` file with those environment variables: - -```bash -TAG=prod FRONTEND_ENV=production bash ./scripts/build-push.sh -``` - -3. **Deploy your stack** - -* Set these environment variables: - * `DOMAIN={{cookiecutter.domain_main}}` - * `TRAEFIK_TAG={{cookiecutter.traefik_constraint_tag}}` - * `STACK_NAME={{cookiecutter.docker_swarm_stack_name_main}}` - * `TAG=prod` -* Use the provided `scripts/deploy.sh` file with those environment variables: - -```bash -DOMAIN={{cookiecutter.domain_main}} \ -TRAEFIK_TAG={{cookiecutter.traefik_constraint_tag}} \ -STACK_NAME={{cookiecutter.docker_swarm_stack_name_main}} \ -TAG=prod \ -bash ./scripts/deploy.sh -``` - ---- - -If you change your mind and, for example, want to deploy everything to a different domain, you only have to change the `DOMAIN` environment variable in the previous commands. If you wanted to add a different version / environment of your stack, like "`preproduction`", you would only have to set `TAG=preproduction` in your command and update these other environment variables accordingly. And it would all work, that way you could have different environments and deployments of the same app in the same cluster. - -#### Deployment Technical Details - -Building and pushing is done with the `docker-compose.yml` file, using the `docker-compose` command. The file `docker-compose.yml` uses the file `.env` with default environment variables. And the scripts set some additional environment variables as well. - -The deployment requires using `docker stack` instead of `docker-swarm`, and it can't read environment variables or `.env` files. Because of that, the `deploy.sh` script generates a file `docker-stack.yml` with the configurations from `docker-compose.yml` and injecting the environment variables in it. And then uses it to deploy the stack. - -You can do the process by hand based on those same scripts if you wanted. The general structure is like this: - -```bash -# Use the environment variables passed to this script, as TAG and FRONTEND_ENV -# And re-create those variables as environment variables for the next command -TAG=${TAG?Variable not set} \ -# Set the environment variable FRONTEND_ENV to the same value passed to this script with -# a default value of "production" if nothing else was passed -FRONTEND_ENV=${FRONTEND_ENV-production?Variable not set} \ -# The actual comand that does the work: docker-compose -docker-compose \ -# Pass the file that should be used, setting explicitly docker-compose.yml avoids the -# default of also using docker-compose.override.yml --f docker-compose.yml \ -# Use the docker-compose sub command named "config", it just uses the docker-compose.yml -# file passed to it and prints their combined contents -# Put those contents in a file "docker-stack.yml", with ">" -config > docker-stack.yml - -# The previous only generated a docker-stack.yml file, -# but didn't do anything with it yet - -# docker-auto-labels makes sure the labels used for constraints exist in the cluster -docker-auto-labels docker-stack.yml - -# Now this command uses that same file to deploy it -docker stack deploy -c docker-stack.yml --with-registry-auth "${STACK_NAME?Variable not set}" -``` - -### Continuous Integration / Continuous Delivery - -If you use GitLab CI, the included `.gitlab-ci.yml` can automatically deploy it. You may need to update it according to your GitLab configurations. - -If you use any other CI / CD provider, you can base your deployment from that `.gitlab-ci.yml` file, as all the actual script steps are performed in `bash` scripts that you can easily re-use. - -GitLab CI is configured assuming 2 environments following GitLab flow: - -* `prod` (production) from the `production` branch. -* `stag` (staging) from the `master` branch. - -If you need to add more environments, for example, you could imagine using a client-approved `preprod` branch, you can just copy the configurations in `.gitlab-ci.yml` for `stag` and rename the corresponding variables. The Docker Compose file and environment variables are configured to support as many environments as you need, so that you only need to modify `.gitlab-ci.yml` (or whichever CI system configuration you are using). - ## Docker Compose files and env vars -There is a main `docker-compose.yml` file with all the configurations that apply to the whole stack, it is used automatically by `docker-compose`. +There is a main `docker-compose.yml` file with all the configurations that apply to the whole stack, it is used automatically by `docker compose`. -And there's also a `docker-compose.override.yml` with overrides for development, for example to mount the source code as a volume. It is used automatically by `docker-compose` to apply overrides on top of `docker-compose.yml`. +And there's also a `docker-compose.override.yml` with overrides for development, for example to mount the source code as a volume. It is used automatically by `docker compose` to apply overrides on top of `docker-compose.yml`. These Docker Compose files use the `.env` file containing configurations to be injected as environment variables in the containers. -They also use some additional configurations taken from environment variables set in the scripts before calling the `docker-compose` command. +They also use some additional configurations taken from environment variables set in the scripts before calling the `docker compose` command. It is all designed to support several "stages", like development, building, testing, and deployment. Also, allowing the deployment to different environments like staging and production (and you can add more environments very easily). -They are designed to have the minimum repetition of code and configurations, so that if you need to change something, you have to change it in the minimum amount of places. That's why files use environment variables that get auto-expanded. That way, if for example, you want to use a different domain, you can call the `docker-compose` command with a different `DOMAIN` environment variable instead of having to change the domain in several places inside the Docker Compose files. +They are designed to have the minimum repetition of code and configurations, so that if you need to change something, you have to change it in the minimum amount of places. That's why files use environment variables that get auto-expanded. That way, if for example, you want to use a different domain, you can call the `docker compose` command with a different `DOMAIN` environment variable instead of having to change the domain in several places inside the Docker Compose files. Also, if you want to have another deployment environment, say `preprod`, you just have to change environment variables, but you can keep using the same Docker Compose files. @@ -697,39 +371,7 @@ One way to do it could be to add each environment variable to your CI/CD system, ## URLs -These are the URLs that will be used and generated by the project. - -### Production URLs - -Production URLs, from the branch `production`. - -Frontend: https://{{cookiecutter.domain_main}} - -Backend: https://{{cookiecutter.domain_main}}/api/ - -Automatic Interactive Docs (Swagger UI): https://{{cookiecutter.domain_main}}/docs - -Automatic Alternative Docs (ReDoc): https://{{cookiecutter.domain_main}}/redoc - -PGAdmin: https://pgadmin.{{cookiecutter.domain_main}} - -Flower: https://flower.{{cookiecutter.domain_main}} - -### Staging URLs - -Staging URLs, from the branch `master`. - -Frontend: https://{{cookiecutter.domain_staging}} - -Backend: https://{{cookiecutter.domain_staging}}/api/ - -Automatic Interactive Docs (Swagger UI): https://{{cookiecutter.domain_staging}}/docs - -Automatic Alternative Docs (ReDoc): https://{{cookiecutter.domain_staging}}/redoc - -PGAdmin: https://pgadmin.{{cookiecutter.domain_staging}} - -Flower: https://flower.{{cookiecutter.domain_staging}} +The production or staging URLs would use these same paths, but with your own domain. ### Development URLs @@ -749,42 +391,6 @@ Flower: http://localhost:5555 Traefik UI: http://localhost:8090 -### Development with Docker Toolbox URLs - -Development URLs, for local development. - -Frontend: http://local.dockertoolbox.tiangolo.com - -Backend: http://local.dockertoolbox.tiangolo.com/api/ - -Automatic Interactive Docs (Swagger UI): https://local.dockertoolbox.tiangolo.com/docs - -Automatic Alternative Docs (ReDoc): https://local.dockertoolbox.tiangolo.com/redoc - -PGAdmin: http://local.dockertoolbox.tiangolo.com:5050 - -Flower: http://local.dockertoolbox.tiangolo.com:5555 - -Traefik UI: http://local.dockertoolbox.tiangolo.com:8090 - -### Development with a custom IP URLs - -Development URLs, for local development. - -Frontend: http://dev.{{cookiecutter.domain_main}} - -Backend: http://dev.{{cookiecutter.domain_main}}/api/ - -Automatic Interactive Docs (Swagger UI): https://dev.{{cookiecutter.domain_main}}/docs - -Automatic Alternative Docs (ReDoc): https://dev.{{cookiecutter.domain_main}}/redoc - -PGAdmin: http://dev.{{cookiecutter.domain_main}}:5050 - -Flower: http://dev.{{cookiecutter.domain_main}}:5555 - -Traefik UI: http://dev.{{cookiecutter.domain_main}}:8090 - ### Development in localhost with a custom domain URLs Development URLs, for local development. @@ -802,32 +408,3 @@ PGAdmin: http://localhost.tiangolo.com:5050 Flower: http://localhost.tiangolo.com:5555 Traefik UI: http://localhost.tiangolo.com:8090 - -## Project generation and updating, or re-generating - -This project was generated using https://github.com/tiangolo/full-stack-fastapi-postgresql with: - -```bash -pip install cookiecutter -cookiecutter https://github.com/tiangolo/full-stack-fastapi-postgresql -``` - -You can check the variables used during generation in the file `cookiecutter-config-file.yml`. - -You can generate the project again with the same configurations used the first time. - -That would be useful if, for example, the project generator (`tiangolo/full-stack-fastapi-postgresql`) was updated and you wanted to integrate or review the changes. - -You could generate a new project with the same configurations as this one in a parallel directory. And compare the differences between the two, without having to overwrite your current code but being able to use the same variables used for your current project. - -To achieve that, the generated project includes the file `cookiecutter-config-file.yml` with the current variables used. - -You can use that file while generating a new project to reuse all those variables. - -For example, run: - -```console -$ cookiecutter --config-file ./cookiecutter-config-file.yml --output-dir ../project-copy https://github.com/tiangolo/full-stack-fastapi-postgresql -``` - -That will use the file `cookiecutter-config-file.yml` in the current directory (in this project) to generate a new project inside a sibling directory `project-copy`. diff --git a/src/backend/app/db/init_db.py b/src/backend/app/db/init_db.py index 8566a29f23..cb438d19b4 100644 --- a/src/backend/app/db/init_db.py +++ b/src/backend/app/db/init_db.py @@ -12,8 +12,13 @@ def init_db(session: Session) -> None: # Tables should be created with Alembic migrations # But if you don't want to use migrations, create - # the tables un-commenting the next line - # Base.metadata.create_all(bind=engine) + # the tables un-commenting the next lines + # from sqlmodel import SQLModel + + # from app.db.engine import engine + # This works because the models are already imported and registered from app.models + # SQLModel.metadata.create_all(engine) + user = session.exec( select(User).where(User.email == settings.FIRST_SUPERUSER) ).first() From 19dfb440e08f9d437cbc87ee6285ca88f6fdcab6 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 26 Feb 2024 09:39:09 -0500 Subject: [PATCH 181/771] =?UTF-8?q?=E2=9C=A8=20Restructure=20folders,=20al?= =?UTF-8?q?low=20editing=20of=20users/items,=20and=20implement=20other=20r?= =?UTF-8?q?efactors=20and=20improvements=20(#603)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reorganize project directory structure * Allow edit users/items, add useAuth and useCustomToast, password confirmation * Minor improvements for consistency * Add 'Cancel' button to UserInformation in editMode * Refactor UserSettings * Enable user password changes and improve error handling * Enable user information update * Add logout to Sidebar in mobile devices, conditional tabs depending on role and other improvements * Add badges * Remove comment * Appearance tab updates * Change badge color * Reset inputs when clicking on 'Cancel' button * Disable actions menu for Superuser when logged in * Modify Logout and update stores --- .../src/components/Admin/AddUser.tsx | 93 ++++++++++++++++ .../src/components/Admin/EditUser.tsx | 100 ++++++++++++++++++ .../components/{ => Common}/ActionsMenu.tsx | 22 ++-- .../Common}/DeleteAlert.tsx | 24 ++--- .../src/components/{ => Common}/Navbar.tsx | 4 +- .../src/components/Common/Sidebar.tsx | 71 +++++++++++++ .../components/{ => Common}/SidebarItems.tsx | 2 +- .../src/components/{ => Common}/UserMenu.tsx | 26 ++--- .../{modals => components/Items}/AddItem.tsx | 42 ++++---- .../src/components/Items/EditItem.tsx | 74 +++++++++++++ src/new-frontend/src/components/Sidebar.tsx | 59 ----------- .../UserSettings}/Appearance.tsx | 15 +-- .../UserSettings/ChangePassword.tsx | 58 ++++++++++ .../UserSettings}/DeleteAccount.tsx | 8 +- .../UserSettings}/DeleteConfirmation.tsx | 20 ++-- .../UserSettings/UserInformation.tsx | 88 +++++++++++++++ src/new-frontend/src/hooks/useAuth.tsx | 33 ++++++ src/new-frontend/src/hooks/useCustomToast.tsx | 20 ++++ src/new-frontend/src/modals/AddUser.tsx | 97 ----------------- src/new-frontend/src/modals/EditItem.tsx | 48 --------- src/new-frontend/src/modals/EditUser.tsx | 60 ----------- src/new-frontend/src/pages/Admin.tsx | 50 ++++----- src/new-frontend/src/pages/Dashboard.tsx | 4 +- src/new-frontend/src/pages/ErrorPage.tsx | 21 ++-- src/new-frontend/src/pages/Items.tsx | 35 +++--- src/new-frontend/src/pages/Layout.tsx | 33 ++---- src/new-frontend/src/pages/Login.tsx | 59 +++++------ .../src/pages/RecoverPassword.tsx | 14 +-- src/new-frontend/src/pages/UserSettings.tsx | 71 ++++++------- .../src/panels/ChangePassword.tsx | 35 ------ .../src/panels/UserInformation.tsx | 50 --------- src/new-frontend/src/store/items-store.tsx | 19 +++- src/new-frontend/src/store/user-store.tsx | 13 ++- src/new-frontend/src/store/users-store.tsx | 13 ++- 34 files changed, 781 insertions(+), 600 deletions(-) create mode 100644 src/new-frontend/src/components/Admin/AddUser.tsx create mode 100644 src/new-frontend/src/components/Admin/EditUser.tsx rename src/new-frontend/src/components/{ => Common}/ActionsMenu.tsx (52%) rename src/new-frontend/src/{modals => components/Common}/DeleteAlert.tsx (75%) rename src/new-frontend/src/components/{ => Common}/Navbar.tsx (94%) create mode 100644 src/new-frontend/src/components/Common/Sidebar.tsx rename src/new-frontend/src/components/{ => Common}/SidebarItems.tsx (96%) rename src/new-frontend/src/components/{ => Common}/UserMenu.tsx (50%) rename src/new-frontend/src/{modals => components/Items}/AddItem.tsx (64%) create mode 100644 src/new-frontend/src/components/Items/EditItem.tsx delete mode 100644 src/new-frontend/src/components/Sidebar.tsx rename src/new-frontend/src/{panels => components/UserSettings}/Appearance.tsx (50%) create mode 100644 src/new-frontend/src/components/UserSettings/ChangePassword.tsx rename src/new-frontend/src/{panels => components/UserSettings}/DeleteAccount.tsx (77%) rename src/new-frontend/src/{modals => components/UserSettings}/DeleteConfirmation.tsx (69%) create mode 100644 src/new-frontend/src/components/UserSettings/UserInformation.tsx create mode 100644 src/new-frontend/src/hooks/useAuth.tsx create mode 100644 src/new-frontend/src/hooks/useCustomToast.tsx delete mode 100644 src/new-frontend/src/modals/AddUser.tsx delete mode 100644 src/new-frontend/src/modals/EditItem.tsx delete mode 100644 src/new-frontend/src/modals/EditUser.tsx delete mode 100644 src/new-frontend/src/panels/ChangePassword.tsx delete mode 100644 src/new-frontend/src/panels/UserInformation.tsx diff --git a/src/new-frontend/src/components/Admin/AddUser.tsx b/src/new-frontend/src/components/Admin/AddUser.tsx new file mode 100644 index 0000000000..315d6ddfe6 --- /dev/null +++ b/src/new-frontend/src/components/Admin/AddUser.tsx @@ -0,0 +1,93 @@ +import React from 'react'; + +import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +import { UserCreate } from '../../client'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useUsersStore } from '../../store/users-store'; +import { ApiError } from '../../client/core/ApiError'; + +interface AddUserProps { + isOpen: boolean; + onClose: () => void; +} + +interface UserCreateForm extends UserCreate { + confirmPassword: string; + +} + +const AddUser: React.FC = ({ isOpen, onClose }) => { + const showToast = useCustomToast(); + const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm(); + const { addUser } = useUsersStore(); + + const onSubmit: SubmitHandler = async (data) => { + if (data.password === data.confirmPassword) { + try { + await addUser(data); + showToast('Success!', 'User created successfully.', 'success'); + reset(); + onClose(); + } catch (err) { + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); + } + } else { + // TODO: Complete when form validation is implemented + console.log("Passwords don't match") + } + } + + return ( + <> + + + + Add User + + + + Email + + + + Full name + + + + Set Password + + + + Confirm Password + + + + + Is superuser? + + + Is active? + + + + + + + + + + + ) +} + +export default AddUser; \ No newline at end of file diff --git a/src/new-frontend/src/components/Admin/EditUser.tsx b/src/new-frontend/src/components/Admin/EditUser.tsx new file mode 100644 index 0000000000..465a856c55 --- /dev/null +++ b/src/new-frontend/src/components/Admin/EditUser.tsx @@ -0,0 +1,100 @@ +import React from 'react'; + +import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +import { ApiError, UserUpdate } from '../../client'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useUsersStore } from '../../store/users-store'; + +interface EditUserProps { + user_id: number; + isOpen: boolean; + onClose: () => void; +} + +interface UserUpdateForm extends UserUpdate { + confirm_password: string; +} + +const EditUser: React.FC = ({ user_id, isOpen, onClose }) => { + const showToast = useCustomToast(); + const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm(); + const { editUser, users } = useUsersStore(); + + const currentUser = users.find((user) => user.id === user_id); + + const onSubmit: SubmitHandler = async (data) => { + if (data.password === data.confirm_password) { + try { + await editUser(user_id, data); + showToast('Success!', 'User updated successfully.', 'success'); + reset(); + onClose(); + } catch (err) { + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); + } + } else { + // TODO: Complete when form validation is implemented + console.log("Passwords don't match") + } + } + + const onCancel = () => { + reset(); + onClose(); + } + + return ( + <> + + + + Edit User + + + + Email + + + + Full name + + + + Password + + + + Confirmation Password + + + + + Is superuser? + + + Is active? + + + + + + + + + + + + ) +} + +export default EditUser; \ No newline at end of file diff --git a/src/new-frontend/src/components/ActionsMenu.tsx b/src/new-frontend/src/components/Common/ActionsMenu.tsx similarity index 52% rename from src/new-frontend/src/components/ActionsMenu.tsx rename to src/new-frontend/src/components/Common/ActionsMenu.tsx index 2a3a2a7d58..267b48e2ce 100644 --- a/src/new-frontend/src/components/ActionsMenu.tsx +++ b/src/new-frontend/src/components/Common/ActionsMenu.tsx @@ -2,33 +2,35 @@ import React from 'react'; import { Button, Menu, MenuButton, MenuItem, MenuList, useDisclosure } from '@chakra-ui/react'; import { BsThreeDotsVertical } from 'react-icons/bs'; -import { FiTrash, FiEdit } from 'react-icons/fi'; +import { FiEdit, FiTrash } from 'react-icons/fi'; + +import EditUser from '../Admin/EditUser'; +import EditItem from '../Items/EditItem'; +import Delete from './DeleteAlert'; -import Delete from '../modals/DeleteAlert'; -import EditUser from '../modals/EditUser'; -import EditItem from '../modals/EditItem'; interface ActionsMenuProps { type: string; id: number; + disabled: boolean; } -const ActionsMenu: React.FC = ({ type, id }) => { +const ActionsMenu: React.FC = ({ type, id, disabled }) => { const editUserModal = useDisclosure(); const deleteModal = useDisclosure(); return ( <> - } variant="unstyled"> + } variant='unstyled'> - }>Edit {type} - } color="ui.danger">Delete {type} + }>Edit {type} + } color='ui.danger'>Delete {type} { - type === "User" ? - : + type === 'User' ? + : } diff --git a/src/new-frontend/src/modals/DeleteAlert.tsx b/src/new-frontend/src/components/Common/DeleteAlert.tsx similarity index 75% rename from src/new-frontend/src/modals/DeleteAlert.tsx rename to src/new-frontend/src/components/Common/DeleteAlert.tsx index a86ee580a3..68ec81c7b2 100644 --- a/src/new-frontend/src/modals/DeleteAlert.tsx +++ b/src/new-frontend/src/components/Common/DeleteAlert.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button, useToast } from '@chakra-ui/react'; +import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; -import { useItemsStore } from '../store/items-store'; -import { useUsersStore } from '../store/users-store'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useItemsStore } from '../../store/items-store'; +import { useUsersStore } from '../../store/users-store'; interface DeleteProps { type: string; @@ -14,7 +15,7 @@ interface DeleteProps { } const Delete: React.FC = ({ type, id, isOpen, onClose }) => { - const toast = useToast(); + const showToast = useCustomToast(); const cancelRef = React.useRef(null); const [isLoading, setIsLoading] = useState(false); const { handleSubmit } = useForm(); @@ -25,20 +26,10 @@ const Delete: React.FC = ({ type, id, isOpen, onClose }) => { setIsLoading(true); try { type === 'Item' ? await deleteItem(id) : await deleteUser(id); - toast({ - title: "Success", - description: `The ${type.toLowerCase()} was deleted successfully.`, - status: "success", - isClosable: true, - }); + showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success'); onClose(); } catch (err) { - toast({ - title: "An error occurred.", - description: `An error occurred while deleting the ${type.toLowerCase()}.`, - status: "error", - isClosable: true, - }); + showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error'); } finally { setIsLoading(false); } @@ -60,6 +51,7 @@ const Delete: React.FC = ({ type, id, isOpen, onClose }) => {
+ {type === 'User' && All items associated with this user will also be permantly deleted. } Are you sure? You will not be able to undo this action. diff --git a/src/new-frontend/src/components/Navbar.tsx b/src/new-frontend/src/components/Common/Navbar.tsx similarity index 94% rename from src/new-frontend/src/components/Navbar.tsx rename to src/new-frontend/src/components/Common/Navbar.tsx index bc130f1f7d..a5f0b0d494 100644 --- a/src/new-frontend/src/components/Navbar.tsx +++ b/src/new-frontend/src/components/Common/Navbar.tsx @@ -3,8 +3,8 @@ import React from 'react'; import { Button, Flex, Icon, Input, InputGroup, InputLeftElement, useDisclosure } from '@chakra-ui/react'; import { FaPlus, FaSearch } from "react-icons/fa"; -import AddUser from '../modals/AddUser'; -import AddItem from '../modals/AddItem'; +import AddUser from '../Admin/AddUser'; +import AddItem from '../Items/AddItem'; interface NavbarProps { type: string; diff --git a/src/new-frontend/src/components/Common/Sidebar.tsx b/src/new-frontend/src/components/Common/Sidebar.tsx new file mode 100644 index 0000000000..c89158d117 --- /dev/null +++ b/src/new-frontend/src/components/Common/Sidebar.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react'; +import { FiLogOut, FiMenu } from 'react-icons/fi'; +import { useNavigate } from 'react-router-dom'; + +import Logo from '../../assets/images/fastapi-logo.svg'; +import useAuth from '../../hooks/useAuth'; +import { useUserStore } from '../../store/user-store'; +import SidebarItems from './SidebarItems'; + +const Sidebar: React.FC = () => { + const bgColor = useColorModeValue('white', '#1a202c'); + const textColor = useColorModeValue('gray', 'white'); + const secBgColor = useColorModeValue('ui.secondary', '#252d3d'); + const { isOpen, onOpen, onClose } = useDisclosure(); + const { user } = useUserStore(); + const { logout } = useAuth(); + const navigate = useNavigate(); + + const handleLogout = async () => { + logout() + navigate('/login'); + }; + + + return ( + <> + {/* Mobile */} + } /> + + + + + + + + logo + + + + Log out + + + { + user?.email && + Logged in as: {user.email} + } + + + + + + {/* Desktop */} + + + + Logo + + + { + user?.email && + Logged in as: {user.email} + } + + + + ); +} + +export default Sidebar; diff --git a/src/new-frontend/src/components/SidebarItems.tsx b/src/new-frontend/src/components/Common/SidebarItems.tsx similarity index 96% rename from src/new-frontend/src/components/SidebarItems.tsx rename to src/new-frontend/src/components/Common/SidebarItems.tsx index 19c1d93cef..c6d71fef51 100644 --- a/src/new-frontend/src/components/SidebarItems.tsx +++ b/src/new-frontend/src/components/Common/SidebarItems.tsx @@ -4,7 +4,7 @@ import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'; import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi'; import { Link, useLocation } from 'react-router-dom'; -import { useUserStore } from '../store/user-store'; +import { useUserStore } from '../../store/user-store'; const items = [ { icon: FiHome, title: 'Dashboard', path: "/" }, diff --git a/src/new-frontend/src/components/UserMenu.tsx b/src/new-frontend/src/components/Common/UserMenu.tsx similarity index 50% rename from src/new-frontend/src/components/UserMenu.tsx rename to src/new-frontend/src/components/Common/UserMenu.tsx index 7f515fc3ed..137968acc7 100644 --- a/src/new-frontend/src/components/UserMenu.tsx +++ b/src/new-frontend/src/components/Common/UserMenu.tsx @@ -1,38 +1,38 @@ import React from 'react'; -import { IconButton } from '@chakra-ui/button'; -import { Box } from '@chakra-ui/layout'; -import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu'; +import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; import { FaUserAstronaut } from 'react-icons/fa'; import { FiLogOut, FiUser } from 'react-icons/fi'; -import { useNavigate } from 'react-router'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; + +import useAuth from '../../hooks/useAuth'; const UserMenu: React.FC = () => { const navigate = useNavigate(); + const { logout } = useAuth(); const handleLogout = async () => { - localStorage.removeItem("access_token"); - navigate("/login"); - // TODO: reset all Zustand states + logout() + navigate('/login'); }; return ( <> - + {/* Desktop */} + } - bg="ui.main" + icon={} + bg='ui.main' isRound /> - } as={Link} to="settings"> + } as={Link} to='settings'> My profile - } onClick={handleLogout} color="ui.danger" fontWeight="bold"> + } onClick={handleLogout} color='ui.danger' fontWeight='bold'> Log out diff --git a/src/new-frontend/src/modals/AddItem.tsx b/src/new-frontend/src/components/Items/AddItem.tsx similarity index 64% rename from src/new-frontend/src/modals/AddItem.tsx rename to src/new-frontend/src/components/Items/AddItem.tsx index c7967019cc..99fc621c22 100644 --- a/src/new-frontend/src/modals/AddItem.tsx +++ b/src/new-frontend/src/components/Items/AddItem.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; -import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useToast } from '@chakra-ui/react'; +import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { ItemCreate } from '../client'; -import { useItemsStore } from '../store/items-store'; +import { ApiError, ItemCreate } from '../../client'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useItemsStore } from '../../store/items-store'; interface AddItemProps { isOpen: boolean; @@ -12,7 +13,7 @@ interface AddItemProps { } const AddItem: React.FC = ({ isOpen, onClose }) => { - const toast = useToast(); + const showToast = useCustomToast(); const [isLoading, setIsLoading] = useState(false); const { register, handleSubmit, reset } = useForm(); const { addItem } = useItemsStore(); @@ -21,21 +22,12 @@ const AddItem: React.FC = ({ isOpen, onClose }) => { setIsLoading(true); try { await addItem(data); - toast({ - title: 'Success!', - description: 'Item created successfully.', - status: 'success', - isClosable: true, - }); + showToast('Success!', 'Item created successfully.', 'success'); reset(); onClose(); } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to create item. Please try again.', - status: 'error', - isClosable: true, - }); + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } finally { setIsLoading(false); } @@ -50,30 +42,32 @@ const AddItem: React.FC = ({ isOpen, onClose }) => { isCentered > - + Add Item - Title + Title - Description + Description - + + + + + + ) +} + +export default EditItem; \ No newline at end of file diff --git a/src/new-frontend/src/components/Sidebar.tsx b/src/new-frontend/src/components/Sidebar.tsx deleted file mode 100644 index 8450fb0a21..0000000000 --- a/src/new-frontend/src/components/Sidebar.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; - -import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, useDisclosure, Text, useColorModeValue } from '@chakra-ui/react'; -import { FiMenu } from 'react-icons/fi'; - -import Logo from "../assets/images/fastapi-logo.svg"; -import SidebarItems from './SidebarItems'; -import { useUserStore } from '../store/user-store'; - - -const Sidebar: React.FC = () => { - const bgColor = useColorModeValue("white", "#1a202c"); - const textColor = useColorModeValue("gray", "white"); - const secBgColor = useColorModeValue("ui.secondary", "#252d3d"); - - const { isOpen, onOpen, onClose } = useDisclosure(); - const { user } = useUserStore(); - - return ( - <> - {/* Mobile */} - } /> - - - - - - - - Logo - - - { - user?.email && - Logged in as: {user.email} - } - - - - - - {/* Desktop */} - - - - Logo - - - { - user?.email && - Logged in as: {user.email} - } - - - - ); -} - -export default Sidebar; diff --git a/src/new-frontend/src/panels/Appearance.tsx b/src/new-frontend/src/components/UserSettings/Appearance.tsx similarity index 50% rename from src/new-frontend/src/panels/Appearance.tsx rename to src/new-frontend/src/components/UserSettings/Appearance.tsx index 07f988069f..9659dd158a 100644 --- a/src/new-frontend/src/panels/Appearance.tsx +++ b/src/new-frontend/src/components/UserSettings/Appearance.tsx @@ -1,23 +1,24 @@ import React from 'react'; -import { Container, Heading, Radio, RadioGroup, Stack, useColorMode } from '@chakra-ui/react'; +import { Badge, Container, Heading, Radio, RadioGroup, Stack, useColorMode } from '@chakra-ui/react'; const Appearance: React.FC = () => { const { colorMode, toggleColorMode } = useColorMode(); return ( <> - - + + Appearance - - Light (default) + {/* TODO: Add system default option */} + + Light ModeDefault - - Dark + + Dark Mode diff --git a/src/new-frontend/src/components/UserSettings/ChangePassword.tsx b/src/new-frontend/src/components/UserSettings/ChangePassword.tsx new file mode 100644 index 0000000000..eb7ca0ad30 --- /dev/null +++ b/src/new-frontend/src/components/UserSettings/ChangePassword.tsx @@ -0,0 +1,58 @@ +import React from 'react'; + +import { Box, Button, Container, FormControl, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { ApiError, UpdatePassword } from '../../client'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useUserStore } from '../../store/user-store'; + +interface UpdatePasswordForm extends UpdatePassword { + confirm_password: string; +} + +const ChangePassword: React.FC = () => { + const color = useColorModeValue('gray.700', 'white'); + const showToast = useCustomToast(); + const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm(); + const { editPassword } = useUserStore(); + + const onSubmit: SubmitHandler = async (data) => { + try { + await editPassword(data); + showToast('Success!', 'Password updated.', 'success'); + reset(); + } catch (err) { + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); + } + + } + + return ( + <> + + + Change Password + + + + Current password + + + + New password + + + + Confirm new password + + + + + + + ); +} +export default ChangePassword; \ No newline at end of file diff --git a/src/new-frontend/src/panels/DeleteAccount.tsx b/src/new-frontend/src/components/UserSettings/DeleteAccount.tsx similarity index 77% rename from src/new-frontend/src/panels/DeleteAccount.tsx rename to src/new-frontend/src/components/UserSettings/DeleteAccount.tsx index 9852c402e5..4c149d7783 100644 --- a/src/new-frontend/src/panels/DeleteAccount.tsx +++ b/src/new-frontend/src/components/UserSettings/DeleteAccount.tsx @@ -2,21 +2,21 @@ import React from 'react'; import { Button, Container, Heading, Text, useDisclosure } from '@chakra-ui/react'; -import DeleteConfirmation from '../modals/DeleteConfirmation'; +import DeleteConfirmation from './DeleteConfirmation'; const DeleteAccount: React.FC = () => { const confirmationModal = useDisclosure(); return ( <> - - + + Delete Account Are you sure you want to delete your account? This action cannot be undone. - diff --git a/src/new-frontend/src/modals/DeleteConfirmation.tsx b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx similarity index 69% rename from src/new-frontend/src/modals/DeleteConfirmation.tsx rename to src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx index 2f9cf9ca00..521b144e2b 100644 --- a/src/new-frontend/src/modals/DeleteConfirmation.tsx +++ b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -1,7 +1,8 @@ import React, { useState } from 'react'; -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button, useToast } from '@chakra-ui/react'; +import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; +import useCustomToast from '../../hooks/useCustomToast'; interface DeleteProps { isOpen: boolean; @@ -9,7 +10,7 @@ interface DeleteProps { } const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { - const toast = useToast(); + const showToast = useCustomToast(); const cancelRef = React.useRef(null); const [isLoading, setIsLoading] = useState(false); const { handleSubmit } = useForm(); @@ -20,12 +21,7 @@ const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { // TODO: Delete user account when API is ready onClose(); } catch (err) { - toast({ - title: "An error occurred.", - description: `An error occurred while deleting your account.`, - status: "error", - isClosable: true, - }); + showToast('An error occurred', 'An error occurred while deleting your account.', 'error'); } finally { setIsLoading(false); } @@ -37,21 +33,21 @@ const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { isOpen={isOpen} onClose={onClose} leastDestructiveRef={cancelRef} - size={{ base: "sm", md: "md" }} + size={{ base: 'sm', md: 'md' }} isCentered > - + Confirmation Required - All your account data will be permanently deleted. If you're sure, please click 'Confirm' to proceed. + All your account data will be permanently deleted. If you're sure, please click 'Confirm' to proceed. - + {editMode && + } + + + + + ); +} + +export default UserInformation; \ No newline at end of file diff --git a/src/new-frontend/src/hooks/useAuth.tsx b/src/new-frontend/src/hooks/useAuth.tsx new file mode 100644 index 0000000000..279b299105 --- /dev/null +++ b/src/new-frontend/src/hooks/useAuth.tsx @@ -0,0 +1,33 @@ +import { useUserStore } from '../store/user-store'; +import { Body_login_login_access_token as AccessToken, LoginService } from '../client'; +import { useUsersStore } from '../store/users-store'; +import { useItemsStore } from '../store/items-store'; + +const useAuth = () => { + const { user, getUser, resetUser } = useUserStore(); + const { resetUsers } = useUsersStore(); + const { resetItems } = useItemsStore(); + + const login = async (data: AccessToken) => { + const response = await LoginService.loginAccessToken({ + formData: data, + }); + localStorage.setItem('access_token', response.access_token); + await getUser(); + }; + + const logout = () => { + localStorage.removeItem('access_token'); + resetUser(); + resetUsers(); + resetItems(); + }; + + const isLoggedIn = () => { + return user !== null; + }; + + return { login, logout, isLoggedIn }; +} + +export default useAuth; \ No newline at end of file diff --git a/src/new-frontend/src/hooks/useCustomToast.tsx b/src/new-frontend/src/hooks/useCustomToast.tsx new file mode 100644 index 0000000000..41e4e78844 --- /dev/null +++ b/src/new-frontend/src/hooks/useCustomToast.tsx @@ -0,0 +1,20 @@ +import { useCallback } from 'react'; + +import { useToast } from '@chakra-ui/react'; + +const useCustomToast = () => { + const toast = useToast(); + + const showToast = useCallback((title: string, description: string, status: 'success' | 'error') => { + toast({ + title, + description, + status, + isClosable: true, + }); + }, [toast]); + + return showToast; +}; + +export default useCustomToast; \ No newline at end of file diff --git a/src/new-frontend/src/modals/AddUser.tsx b/src/new-frontend/src/modals/AddUser.tsx deleted file mode 100644 index 33e38b21a8..0000000000 --- a/src/new-frontend/src/modals/AddUser.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useState } from 'react'; - -import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useToast } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; - -import { UserCreate } from '../client'; -import { useUsersStore } from '../store/users-store'; - -interface AddUserProps { - isOpen: boolean; - onClose: () => void; -} - -const AddUser: React.FC = ({ isOpen, onClose }) => { - const toast = useToast(); - const [isLoading, setIsLoading] = useState(false); - const { register, handleSubmit, reset } = useForm(); - const { addUser } = useUsersStore(); - - const onSubmit: SubmitHandler = async (data) => { - setIsLoading(true); - try { - await addUser(data); - toast({ - title: 'Success!', - description: 'User created successfully.', - status: 'success', - isClosable: true, - }); - reset(); - onClose(); - - } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to create user. Please try again.', - status: 'error', - isClosable: true, - }); - } finally { - setIsLoading(false); - } - } - - return ( - <> - - - - {/* TODO: Check passwords */} - Add User - - - - Email - - - - Full name - - - - Set Password - - - - Confirm Password - - - - - Is superuser? - - - Is active? - - - - - - - - - - - - ) -} - -export default AddUser; \ No newline at end of file diff --git a/src/new-frontend/src/modals/EditItem.tsx b/src/new-frontend/src/modals/EditItem.tsx deleted file mode 100644 index 73753bf461..0000000000 --- a/src/new-frontend/src/modals/EditItem.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; - -import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; - -interface EditItemProps { - isOpen: boolean; - onClose: () => void; -} - -const EditItem: React.FC = ({ isOpen, onClose }) => { - - return ( - <> - - - - Edit Item - - - - Item - - - - - Description - - - - - - - - - - - - ) -} - -export default EditItem; \ No newline at end of file diff --git a/src/new-frontend/src/modals/EditUser.tsx b/src/new-frontend/src/modals/EditUser.tsx deleted file mode 100644 index 385d39f034..0000000000 --- a/src/new-frontend/src/modals/EditUser.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; - -import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure } from '@chakra-ui/react'; - -interface EditUserProps { - isOpen: boolean; - onClose: () => void; -} - -const EditUser: React.FC = ({ isOpen, onClose }) => { - - return ( - <> - - - - Edit User - - - - Email - - - - - Full name - - - - Password - - - - - Is superuser? - - - Is active? - - - - - - - - - - - - ) -} - -export default EditUser; \ No newline at end of file diff --git a/src/new-frontend/src/pages/Admin.tsx b/src/new-frontend/src/pages/Admin.tsx index ee97f863f5..69ec315cd0 100644 --- a/src/new-frontend/src/pages/Admin.tsx +++ b/src/new-frontend/src/pages/Admin.tsx @@ -1,15 +1,19 @@ import React, { useEffect, useState } from 'react'; -import { Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr, useToast } from '@chakra-ui/react'; +import { Badge, Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import ActionsMenu from '../components/ActionsMenu'; -import Navbar from '../components/Navbar'; +import { ApiError } from '../client'; +import ActionsMenu from '../components/Common/ActionsMenu'; +import Navbar from '../components/Common/Navbar'; +import useCustomToast from '../hooks/useCustomToast'; +import { useUserStore } from '../store/user-store'; import { useUsersStore } from '../store/users-store'; const Admin: React.FC = () => { - const toast = useToast(); + const showToast = useCustomToast(); const [isLoading, setIsLoading] = useState(false); const { users, getUsers } = useUsersStore(); + const { user: currentUser } = useUserStore(); useEffect(() => { const fetchUsers = async () => { @@ -17,12 +21,8 @@ const Admin: React.FC = () => { try { await getUsers(); } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to fetch users. Please try again.', - status: 'error', - isClosable: true, - }); + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } finally { setIsLoading(false); } @@ -36,18 +36,18 @@ const Admin: React.FC = () => { <> {isLoading ? ( // TODO: Add skeleton - - + + ) : ( users && - - + + User Management - + - +
@@ -60,23 +60,23 @@ const Admin: React.FC = () => { {users.map((user) => ( - + - + ))} diff --git a/src/new-frontend/src/pages/Dashboard.tsx b/src/new-frontend/src/pages/Dashboard.tsx index c5b452c7df..9bbb46a082 100644 --- a/src/new-frontend/src/pages/Dashboard.tsx +++ b/src/new-frontend/src/pages/Dashboard.tsx @@ -10,8 +10,8 @@ const Dashboard: React.FC = () => { return ( <> - - Hi, {user?.full_name || user?.email} 👋🏼 + + Hi, {user?.full_name || user?.email} 👋🏼 Welcome back, nice to see you again! diff --git a/src/new-frontend/src/pages/ErrorPage.tsx b/src/new-frontend/src/pages/ErrorPage.tsx index f8bd796ec2..cc744df2cc 100644 --- a/src/new-frontend/src/pages/ErrorPage.tsx +++ b/src/new-frontend/src/pages/ErrorPage.tsx @@ -1,6 +1,5 @@ -import { Button, Container, Text } from "@chakra-ui/react"; - -import { Link, useRouteError } from "react-router-dom"; +import { Button, Container, Text } from '@chakra-ui/react'; +import { Link, useRouteError } from 'react-router-dom'; const ErrorPage: React.FC = () => { const error = useRouteError(); @@ -8,14 +7,14 @@ const ErrorPage: React.FC = () => { return ( <> - - Oops! - Houston, we have a problem. - An unexpected error has occurred. - {error.statusText || error.message} - + + Oops! + Houston, we have a problem. + An unexpected error has occurred. + {/* {error.statusText || error.message} */} + ); diff --git a/src/new-frontend/src/pages/Items.tsx b/src/new-frontend/src/pages/Items.tsx index e3e273b0cd..3aab0d21a0 100644 --- a/src/new-frontend/src/pages/Items.tsx +++ b/src/new-frontend/src/pages/Items.tsx @@ -1,14 +1,15 @@ import React, { useEffect, useState } from 'react'; -import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr, useToast } from '@chakra-ui/react'; +import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import ActionsMenu from '../components/ActionsMenu'; -import Navbar from '../components/Navbar'; +import { ApiError } from '../client'; +import ActionsMenu from '../components/Common/ActionsMenu'; +import Navbar from '../components/Common/Navbar'; +import useCustomToast from '../hooks/useCustomToast'; import { useItemsStore } from '../store/items-store'; - const Items: React.FC = () => { - const toast = useToast(); + const showToast = useCustomToast(); const [isLoading, setIsLoading] = useState(false); const { items, getItems } = useItemsStore(); @@ -18,12 +19,8 @@ const Items: React.FC = () => { try { await getItems(); } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to fetch items. Please try again.', - status: 'error', - isClosable: true, - }); + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } finally { setIsLoading(false); } @@ -38,18 +35,18 @@ const Items: React.FC = () => { <> {isLoading ? ( // TODO: Add skeleton - - + + ) : ( items && - - + + Items Management - + -
Full name
{user.full_name || "N/A"}{user.full_name || 'N/A'}{currentUser?.id === user.id && You} {user.email}{user.is_superuser ? "Superuser" : "User"}{user.is_superuser ? 'Superuser' : 'User'} - {user.is_active ? "Active" : "Inactive"} + {user.is_active ? 'Active' : 'Inactive'} - +
+
@@ -63,9 +60,9 @@ const Items: React.FC = () => { - + ))} diff --git a/src/new-frontend/src/pages/Layout.tsx b/src/new-frontend/src/pages/Layout.tsx index 06471fb050..db2815001e 100644 --- a/src/new-frontend/src/pages/Layout.tsx +++ b/src/new-frontend/src/pages/Layout.tsx @@ -1,37 +1,26 @@ -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; +import { Flex } from '@chakra-ui/react'; import { Outlet } from 'react-router-dom'; -import Sidebar from '../components/Sidebar'; -import { Flex, useToast } from '@chakra-ui/react'; +import Sidebar from '../components/Common/Sidebar'; +import UserMenu from '../components/Common/UserMenu'; import { useUserStore } from '../store/user-store'; -import UserMenu from '../components/UserMenu'; const Layout: React.FC = () => { - const toast = useToast(); const { getUser } = useUserStore(); useEffect(() => { - const fetchUser = async () => { - const token = localStorage.getItem('access_token'); - if (token) { - try { - await getUser(); - } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to fetch user. Please try again.', - status: 'error', - isClosable: true, - }); - } - } + const token = localStorage.getItem('access_token'); + if (token) { + (async () => { + await getUser(); + })(); } - fetchUser(); - }, []); + }, [getUser]); return ( - + diff --git a/src/new-frontend/src/pages/Login.tsx b/src/new-frontend/src/pages/Login.tsx index b9e89803e2..83053ddc3a 100644 --- a/src/new-frontend/src/pages/Login.tsx +++ b/src/new-frontend/src/pages/Login.tsx @@ -1,68 +1,67 @@ -import React from "react"; +import React from 'react'; -import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; -import { Button, Center, Container, FormControl, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from "@chakra-ui/react"; -import { SubmitHandler, useForm } from "react-hook-form"; -import { Link as ReactRouterLink, useNavigate } from "react-router-dom"; +import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'; +import { Button, Center, Container, FormControl, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { Link as ReactRouterLink, useNavigate } from 'react-router-dom'; -import Logo from "../assets/images/fastapi-logo.svg"; -import { LoginService } from "../client"; -import { Body_login_login_access_token as AccessToken } from "../client/models/Body_login_login_access_token"; +import Logo from '../assets/images/fastapi-logo.svg'; +import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token'; +import useAuth from '../hooks/useAuth'; const Login: React.FC = () => { const [show, setShow] = useBoolean(); const navigate = useNavigate(); const { register, handleSubmit } = useForm(); + const { login } = useAuth(); + const onSubmit: SubmitHandler = async (data) => { - const response = await LoginService.loginAccessToken({ - formData: data, - }); - localStorage.setItem("access_token", response.access_token); - navigate("/"); + await login(data); + navigate('/'); }; return ( <> - - - + + + - + - + {show ? : }
- + Forgot password?
-
diff --git a/src/new-frontend/src/pages/RecoverPassword.tsx b/src/new-frontend/src/pages/RecoverPassword.tsx index 23c44124b3..cbe8ff77e8 100644 --- a/src/new-frontend/src/pages/RecoverPassword.tsx +++ b/src/new-frontend/src/pages/RecoverPassword.tsx @@ -1,9 +1,10 @@ import React from "react"; -import { Button, Container, FormControl, Heading, Input, Text, useToast } from "@chakra-ui/react"; +import { Button, Container, FormControl, Heading, Input, Text } from "@chakra-ui/react"; import { SubmitHandler, useForm } from "react-hook-form"; import { LoginService } from "../client"; +import useCustomToast from "../hooks/useCustomToast"; interface FormData { email: string; @@ -11,20 +12,15 @@ interface FormData { const RecoverPassword: React.FC = () => { const { register, handleSubmit } = useForm(); - const toast = useToast(); + const showToast = useCustomToast(); const onSubmit: SubmitHandler = async (data) => { const response = await LoginService.recoverPassword({ email: data.email, }); - console.log(response); + console.log(response) - toast({ - title: "Email sent.", - description: "We sent an email with a link to get back into your account.", - status: "success", - isClosable: true, - }); + showToast("Email sent.", "We sent an email with a link to get back into your account.", "success"); }; return ( diff --git a/src/new-frontend/src/pages/UserSettings.tsx b/src/new-frontend/src/pages/UserSettings.tsx index 49c551ae69..ea096cf77e 100644 --- a/src/new-frontend/src/pages/UserSettings.tsx +++ b/src/new-frontend/src/pages/UserSettings.tsx @@ -1,49 +1,46 @@ import React from 'react'; import { Container, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; +import Appearance from '../components/UserSettings/Appearance'; +import ChangePassword from '../components/UserSettings/ChangePassword'; +import DeleteAccount from '../components/UserSettings/DeleteAccount'; +import UserInformation from '../components/UserSettings/UserInformation'; +import { useUserStore } from '../store/user-store'; + +const tabsConfig = [ + { title: 'My profile', component: UserInformation }, + { title: 'Password', component: ChangePassword }, + { title: 'Appearance', component: Appearance }, + { title: 'Danger zone', component: DeleteAccount }, +]; -import Appearance from '../panels/Appearance'; -import ChangePassword from '../panels/ChangePassword'; -import DeleteAccount from '../panels/DeleteAccount'; -import UserInformation from '../panels/UserInformation'; - +const UserSettings: React.FC = () => { + const { user } = useUserStore(); + const finalTabs = user?.is_superuser ? tabsConfig.slice(0, 3) : tabsConfig; -const UserSettings: React.FC = () => { return ( - <> - - - User Settings - - - - My profile - Password - Appearance - Danger zone - - - - - - - + + + User Settings + + + + {finalTabs.map((tab, index) => ( + {tab.title} + ))} + + + {finalTabs.map((tab, index) => ( + + - - - - - - - - - - - + ))} + + + ); }; -export default UserSettings; - +export default UserSettings; \ No newline at end of file diff --git a/src/new-frontend/src/panels/ChangePassword.tsx b/src/new-frontend/src/panels/ChangePassword.tsx deleted file mode 100644 index 19bef94d66..0000000000 --- a/src/new-frontend/src/panels/ChangePassword.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -import { Box, Button, Container, FormControl, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; - -const ChangePassword: React.FC = () => { - const color = useColorModeValue("gray.700", "white"); - - return ( - <> - - - Change Password - - - - Old password - - - - New password - - - - Confirm new password - - - - - - - ); -} -export default ChangePassword; \ No newline at end of file diff --git a/src/new-frontend/src/panels/UserInformation.tsx b/src/new-frontend/src/panels/UserInformation.tsx deleted file mode 100644 index ff73912503..0000000000 --- a/src/new-frontend/src/panels/UserInformation.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useState } from 'react'; - -import { Button, Container, FormControl, FormLabel, Heading, Input, Text, useColorModeValue } from '@chakra-ui/react'; - -import { useUserStore } from '../store/user-store'; - -const UserInformation: React.FC = () => { - const color = useColorModeValue("gray.700", "white"); - const [editMode, setEditMode] = useState(false); - const { user } = useUserStore(); - - - const toggleEditMode = () => { - setEditMode(!editMode); - }; - - return ( - <> - - - User Information - - - Full name - { - editMode ? - : - - {user?.full_name || "N/A"} - - } - - - Email - { - editMode ? - : - - {user?.email || "N/A"} - - } - - - - - ); -} -export default UserInformation; \ No newline at end of file diff --git a/src/new-frontend/src/store/items-store.tsx b/src/new-frontend/src/store/items-store.tsx index dbd6ac708f..9848d6919a 100644 --- a/src/new-frontend/src/store/items-store.tsx +++ b/src/new-frontend/src/store/items-store.tsx @@ -1,11 +1,13 @@ -import { create } from "zustand"; -import { ItemCreate, ItemOut, ItemsService } from "../client"; +import { create } from 'zustand'; +import { ItemCreate, ItemOut, ItemUpdate, ItemsService } from '../client'; -interface ItemsStore { +interface ItemsStore { items: ItemOut[]; getItems: () => Promise; addItem: (item: ItemCreate) => Promise; + editItem: (id: number, item: ItemUpdate) => Promise; deleteItem: (id: number) => Promise; + resetItems: () => void; } export const useItemsStore = create((set) => ({ @@ -15,11 +17,20 @@ export const useItemsStore = create((set) => ({ set({ items: itemsResponse }); }, addItem: async (item: ItemCreate) => { - const itemResponse = await ItemsService.createItem({ requestBody: item}); + const itemResponse = await ItemsService.createItem({ requestBody: item }); set((state) => ({ items: [...state.items, itemResponse] })); }, + editItem: async (id: number, item: ItemUpdate) => { + const itemResponse = await ItemsService.updateItem({ id: id, requestBody: item }); + set((state) => ({ + items: state.items.map((item) => (item.id === id ? itemResponse : item)) + })); + }, deleteItem: async (id: number) => { await ItemsService.deleteItem({ id }); set((state) => ({ items: state.items.filter((item) => item.id !== id) })); + }, + resetItems: () => { + set({ items: [] }); } })); \ No newline at end of file diff --git a/src/new-frontend/src/store/user-store.tsx b/src/new-frontend/src/store/user-store.tsx index 0d4312de2e..4e41dd9937 100644 --- a/src/new-frontend/src/store/user-store.tsx +++ b/src/new-frontend/src/store/user-store.tsx @@ -1,9 +1,11 @@ -import { create } from "zustand"; -import { UserOut, UsersService } from "../client"; +import { create } from 'zustand'; +import { UpdatePassword, UserOut, UserUpdateMe, UsersService } from '../client'; interface UserStore { user: UserOut | null; getUser: () => Promise; + editUser: (user: UserUpdateMe) => Promise; + editPassword: (password: UpdatePassword) => Promise; resetUser: () => void; } @@ -13,6 +15,13 @@ export const useUserStore = create((set) => ({ const user = await UsersService.readUserMe(); set({ user }); }, + editUser: async (user: UserUpdateMe) => { + const updatedUser = await UsersService.updateUserMe({ requestBody: user }); + set((state) => ({ user: { ...state.user, ...updatedUser } })); + }, + editPassword: async (password: UpdatePassword) => { + await UsersService.updatePasswordMe({ requestBody: password }); + }, resetUser: () => { set({ user: null }); } diff --git a/src/new-frontend/src/store/users-store.tsx b/src/new-frontend/src/store/users-store.tsx index eb3ee61e76..4a6cf66bd3 100644 --- a/src/new-frontend/src/store/users-store.tsx +++ b/src/new-frontend/src/store/users-store.tsx @@ -1,11 +1,13 @@ import { create } from "zustand"; -import { UserCreate, UserOut, UsersService } from "../client"; +import { UserCreate, UserOut, UserUpdate, UsersService } from "../client"; interface UsersStore { users: UserOut[]; getUsers: () => Promise; addUser: (user: UserCreate) => Promise; + editUser: (id: number, user: UserUpdate) => Promise; deleteUser: (id: number) => Promise; + resetUsers: () => void; } export const useUsersStore = create((set) => ({ @@ -18,8 +20,17 @@ export const useUsersStore = create((set) => ({ const userResponse = await UsersService.createUser({ requestBody: user }); set((state) => ({ users: [...state.users, userResponse] })); }, + editUser: async (id: number, user: UserUpdate) => { + const userResponse = await UsersService.updateUser({ userId: id, requestBody: user }); + set((state) => ({ + users: state.users.map((user) => (user.id === id ? userResponse : user)) + })); + }, deleteUser: async (id: number) => { await UsersService.deleteUser({ userId: id }); set((state) => ({ users: state.users.filter((user) => user.id !== id) })); + }, + resetUsers: () => { + set({ users: [] }); } })) \ No newline at end of file From ac912853c045d96918021ecd0e502a139bec8342 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:47:42 -0500 Subject: [PATCH 182/771] =?UTF-8?q?=E2=9C=A8=20Support=20delete=20own=20ac?= =?UTF-8?q?count=20and=20other=20tweaks=20(#614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Esteban Maya Cadavid --- src/backend/app/api/api_v1/endpoints/users.py | 19 ++++++++++-------- .../src/components/Common/Sidebar.tsx | 3 --- .../src/components/Common/UserMenu.tsx | 4 +--- .../UserSettings/DeleteConfirmation.tsx | 20 +++++++++++-------- src/new-frontend/src/hooks/useAuth.tsx | 4 ++++ src/new-frontend/src/store/items-store.tsx | 2 +- src/new-frontend/src/store/users-store.tsx | 2 +- 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/backend/app/api/api_v1/endpoints/users.py b/src/backend/app/api/api_v1/endpoints/users.py index 0b9a51127e..05b61d3047 100644 --- a/src/backend/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/api/api_v1/endpoints/users.py @@ -1,7 +1,7 @@ from typing import Any from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import func, select +from sqlmodel import func, select, delete from app import crud from app.api.deps import ( @@ -21,6 +21,7 @@ UsersOut, UserUpdate, UserUpdateMe, + Item ) from app.utils import send_new_account_email @@ -194,12 +195,14 @@ def delete_user( user = session.get(User, user_id) if not user: raise HTTPException(status_code=404, detail="User not found") - if not current_user.is_superuser: - raise HTTPException(status_code=400, detail="Not enough permissions") - if user == current_user: + + if (user == current_user and not current_user.is_superuser) or (user != current_user and current_user.is_superuser): + statement = delete(Item).where(Item.owner_id == user_id) + session.exec(statement) + session.delete(user) + session.commit() + return Message(message="User deleted successfully") + elif user == current_user and current_user.is_superuser: raise HTTPException( - status_code=400, detail="Users are not allowed to delete themselves" + status_code=400, detail="Super users are not allowed to delete themselves" ) - session.delete(user) - session.commit() - return Message(message="User deleted successfully") diff --git a/src/new-frontend/src/components/Common/Sidebar.tsx b/src/new-frontend/src/components/Common/Sidebar.tsx index c89158d117..e0c0e222b0 100644 --- a/src/new-frontend/src/components/Common/Sidebar.tsx +++ b/src/new-frontend/src/components/Common/Sidebar.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react'; import { FiLogOut, FiMenu } from 'react-icons/fi'; -import { useNavigate } from 'react-router-dom'; import Logo from '../../assets/images/fastapi-logo.svg'; import useAuth from '../../hooks/useAuth'; @@ -16,11 +15,9 @@ const Sidebar: React.FC = () => { const { isOpen, onOpen, onClose } = useDisclosure(); const { user } = useUserStore(); const { logout } = useAuth(); - const navigate = useNavigate(); const handleLogout = async () => { logout() - navigate('/login'); }; diff --git a/src/new-frontend/src/components/Common/UserMenu.tsx b/src/new-frontend/src/components/Common/UserMenu.tsx index 137968acc7..31b6928c56 100644 --- a/src/new-frontend/src/components/Common/UserMenu.tsx +++ b/src/new-frontend/src/components/Common/UserMenu.tsx @@ -3,17 +3,15 @@ import React from 'react'; import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; import { FaUserAstronaut } from 'react-icons/fa'; import { FiLogOut, FiUser } from 'react-icons/fi'; -import { Link, useNavigate } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import useAuth from '../../hooks/useAuth'; const UserMenu: React.FC = () => { - const navigate = useNavigate(); const { logout } = useAuth(); const handleLogout = async () => { logout() - navigate('/login'); }; return ( diff --git a/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx index 521b144e2b..e0edb53820 100644 --- a/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx +++ b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -2,7 +2,10 @@ import React, { useState } from 'react'; import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; +import { ApiError } from '../../client'; +import useAuth from '../../hooks/useAuth'; import useCustomToast from '../../hooks/useCustomToast'; +import { useUserStore } from '../../store/user-store'; interface DeleteProps { isOpen: boolean; @@ -12,18 +15,19 @@ interface DeleteProps { const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { const showToast = useCustomToast(); const cancelRef = React.useRef(null); - const [isLoading, setIsLoading] = useState(false); - const { handleSubmit } = useForm(); + const { handleSubmit, formState: { isSubmitting } } = useForm(); + const { user, deleteUser } = useUserStore(); + const { logout } = useAuth(); const onSubmit = async () => { - setIsLoading(true); try { - // TODO: Delete user account when API is ready + await deleteUser(user!.id); + logout(); onClose(); + showToast('Success', 'Your account has been successfully deleted.', 'success'); } catch (err) { - showToast('An error occurred', 'An error occurred while deleting your account.', 'error'); - } finally { - setIsLoading(false); + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } } @@ -47,7 +51,7 @@ const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { - diff --git a/src/new-frontend/src/components/Admin/EditUser.tsx b/src/new-frontend/src/components/Admin/EditUser.tsx index 465a856c55..06a818f803 100644 --- a/src/new-frontend/src/components/Admin/EditUser.tsx +++ b/src/new-frontend/src/components/Admin/EditUser.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { ApiError, UserUpdate } from '../../client'; @@ -19,25 +19,34 @@ interface UserUpdateForm extends UserUpdate { const EditUser: React.FC = ({ user_id, isOpen, onClose }) => { const showToast = useCustomToast(); - const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm(); const { editUser, users } = useUsersStore(); - const currentUser = users.find((user) => user.id === user_id); + const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + email: currentUser?.email, + full_name: currentUser?.full_name, + password: '', + confirm_password: '', + is_superuser: currentUser?.is_superuser, + is_active: currentUser?.is_active + } + }); + const onSubmit: SubmitHandler = async (data) => { - if (data.password === data.confirm_password) { - try { - await editUser(user_id, data); - showToast('Success!', 'User updated successfully.', 'success'); - reset(); - onClose(); - } catch (err) { - const errDetail = (err as ApiError).body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); + try { + if (data.password === '') { + delete data.password; } - } else { - // TODO: Complete when form validation is implemented - console.log("Passwords don't match") + await editUser(user_id, data); + showToast('Success!', 'User updated successfully.', 'success'); + reset(); + onClose(); + } catch (err) { + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } } @@ -59,28 +68,33 @@ const EditUser: React.FC = ({ user_id, isOpen, onClose }) => { Edit User - + Email - + + {errors.email && {errors.email.message}} Full name - + - - Password - + + Set Password + + {errors.password && {errors.password.message}} - - Confirmation Password - + + Confirm Password + value === getValues().password || 'The passwords do not match' + })} placeholder='••••••••' type='password' /> + {errors.confirm_password && {errors.confirm_password.message}} - Is superuser? + Is superuser? - Is active? + Is active? diff --git a/src/new-frontend/src/components/Common/DeleteAlert.tsx b/src/new-frontend/src/components/Common/DeleteAlert.tsx index 68ec81c7b2..f6c70cdebf 100644 --- a/src/new-frontend/src/components/Common/DeleteAlert.tsx +++ b/src/new-frontend/src/components/Common/DeleteAlert.tsx @@ -17,21 +17,17 @@ interface DeleteProps { const Delete: React.FC = ({ type, id, isOpen, onClose }) => { const showToast = useCustomToast(); const cancelRef = React.useRef(null); - const [isLoading, setIsLoading] = useState(false); - const { handleSubmit } = useForm(); + const { handleSubmit, formState: {isSubmitting} } = useForm(); const { deleteItem } = useItemsStore(); const { deleteUser } = useUsersStore(); const onSubmit = async () => { - setIsLoading(true); try { type === 'Item' ? await deleteItem(id) : await deleteUser(id); showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success'); onClose(); } catch (err) { showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error'); - } finally { - setIsLoading(false); } } @@ -56,10 +52,10 @@ const Delete: React.FC = ({ type, id, isOpen, onClose }) => { - - diff --git a/src/new-frontend/src/components/Items/AddItem.tsx b/src/new-frontend/src/components/Items/AddItem.tsx index 99fc621c22..3c62a87c94 100644 --- a/src/new-frontend/src/components/Items/AddItem.tsx +++ b/src/new-frontend/src/components/Items/AddItem.tsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; +import React from 'react'; -import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import { Button, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { ApiError, ItemCreate } from '../../client'; @@ -14,12 +14,17 @@ interface AddItemProps { const AddItem: React.FC = ({ isOpen, onClose }) => { const showToast = useCustomToast(); - const [isLoading, setIsLoading] = useState(false); - const { register, handleSubmit, reset } = useForm(); + const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + title: '', + description: '', + }, + }); const { addItem } = useItemsStore(); const onSubmit: SubmitHandler = async (data) => { - setIsLoading(true); try { await addItem(data); showToast('Success!', 'Item created successfully.', 'success'); @@ -28,8 +33,6 @@ const AddItem: React.FC = ({ isOpen, onClose }) => { } catch (err) { const errDetail = (err as ApiError).body.detail; showToast('Something went wrong.', `${errDetail}`, 'error'); - } finally { - setIsLoading(false); } }; @@ -46,14 +49,15 @@ const AddItem: React.FC = ({ isOpen, onClose }) => { Add Item - + Title + {errors.title && {errors.title.message}} Description @@ -67,10 +71,10 @@ const AddItem: React.FC = ({ isOpen, onClose }) => { - - diff --git a/src/new-frontend/src/components/Items/EditItem.tsx b/src/new-frontend/src/components/Items/EditItem.tsx index ce2d84beb4..d72d3ef780 100644 --- a/src/new-frontend/src/components/Items/EditItem.tsx +++ b/src/new-frontend/src/components/Items/EditItem.tsx @@ -15,10 +15,9 @@ interface EditItemProps { const EditItem: React.FC = ({ id, isOpen, onClose }) => { const showToast = useCustomToast(); - const { register, handleSubmit, reset, formState: { isSubmitting }, } = useForm(); const { editItem, items } = useItemsStore(); - const currentItem = items.find((item) => item.id === id); + const { register, handleSubmit, reset, formState: { isSubmitting }, } = useForm({ defaultValues: { title: currentItem?.title, description: currentItem?.description } }); const onSubmit: SubmitHandler = async (data) => { try { @@ -52,11 +51,11 @@ const EditItem: React.FC = ({ id, isOpen, onClose }) => { Title - + Description - + diff --git a/src/new-frontend/src/components/UserSettings/Appearance.tsx b/src/new-frontend/src/components/UserSettings/Appearance.tsx index 9659dd158a..b1f8fb7052 100644 --- a/src/new-frontend/src/components/UserSettings/Appearance.tsx +++ b/src/new-frontend/src/components/UserSettings/Appearance.tsx @@ -15,10 +15,10 @@ const Appearance: React.FC = () => { {/* TODO: Add system default option */} - Light ModeDefault + Light modeDefault - Dark Mode + Dark mode diff --git a/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx index e0edb53820..6aa846a0e2 100644 --- a/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx +++ b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; @@ -54,7 +54,7 @@ const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { - diff --git a/src/new-frontend/src/components/UserSettings/UserInformation.tsx b/src/new-frontend/src/components/UserSettings/UserInformation.tsx index de12474926..04e2ff67f2 100644 --- a/src/new-frontend/src/components/UserSettings/UserInformation.tsx +++ b/src/new-frontend/src/components/UserSettings/UserInformation.tsx @@ -75,7 +75,7 @@ const UserInformation: React.FC = () => { {editMode ? 'Save' : 'Edit'} {editMode && - }
diff --git a/src/new-frontend/src/pages/Login.tsx b/src/new-frontend/src/pages/Login.tsx index 83053ddc3a..4563c6db32 100644 --- a/src/new-frontend/src/pages/Login.tsx +++ b/src/new-frontend/src/pages/Login.tsx @@ -1,23 +1,35 @@ import React from 'react'; import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'; -import { Button, Center, Container, FormControl, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react'; +import { Button, Center, Container, FormControl, FormErrorMessage, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { Link as ReactRouterLink, useNavigate } from 'react-router-dom'; +import { Link as ReactRouterLink } from 'react-router-dom'; import Logo from '../assets/images/fastapi-logo.svg'; +import { ApiError } from '../client'; import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token'; import useAuth from '../hooks/useAuth'; const Login: React.FC = () => { const [show, setShow] = useBoolean(); - const navigate = useNavigate(); - const { register, handleSubmit } = useForm(); + const [error, setError] = React.useState(null); + const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + username: '', + password: '' + } + }); const { login } = useAuth(); const onSubmit: SubmitHandler = async (data) => { - await login(data); - navigate('/'); + try { + await login(data); + } catch (err) { + const errDetail = (err as ApiError).body.detail; + setError(errDetail) + } }; return ( @@ -32,11 +44,12 @@ const Login: React.FC = () => { gap={4} centerContent > - - - + + + + {errors.username && {errors.username.message}} - + { -
- - Forgot password? - -
+ {error && + {error} + }
- From c32e13613fd52c08e37425cf6f6b29d79271ec33 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:49:28 +0000 Subject: [PATCH 185/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 815e9b8d13..4738a19b34 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add form validation to Admin, Items and Login. PR [#616](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/616) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sidebar to new frontend. PR [#587](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/587) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Login to new frontend. PR [#585](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/585) by [@alejsdev](https://github.com/alejsdev). * ✨ Include schemas in generated frontend client. PR [#584](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/584) by [@alejsdev](https://github.com/alejsdev). From e1642caa3b3c67a8dda5edf45887fb198ea05645 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:53:07 +0000 Subject: [PATCH 186/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4738a19b34..f1b05f5fce 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ After using this generator, your new project (the directory created) will contai ### Latest Changes +* ⬆ Bump tiangolo/issue-manager from 0.2.0 to 0.5.0. PR [#591](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/591) by [@dependabot[bot]](https://github.com/apps/dependabot). * ✨ Upgrade items router with new SQLModel models, simplified logic, and new FastAPI Annotated dependencies. PR [#560](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/560) by [@tiangolo](https://github.com/tiangolo). * ✨ Adopt SQLModel, create models, start using it. PR [#559](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/559) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Upgrade Python version and dependencies. PR [#558](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/558) by [@tiangolo](https://github.com/tiangolo). From 2c9f472d4331c9cfb08c29d86cbf3ab510559311 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:53:19 +0000 Subject: [PATCH 187/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f1b05f5fce..5c2782c304 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add state store to new frontend. PR [#592](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/592) by [@alejsdev](https://github.com/alejsdev). * ✨ Add form validation to Admin, Items and Login. PR [#616](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/616) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sidebar to new frontend. PR [#587](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/587) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Login to new frontend. PR [#585](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/585) by [@alejsdev](https://github.com/alejsdev). From ba23382735160a27589edb2d29bb5f5af832ab39 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:53:22 +0000 Subject: [PATCH 188/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5c2782c304..90bc09f88c 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add delete_user; refactor delete_item. PR [#594](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/594) by [@alejsdev](https://github.com/alejsdev). * ✨ Add state store to new frontend. PR [#592](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/592) by [@alejsdev](https://github.com/alejsdev). * ✨ Add form validation to Admin, Items and Login. PR [#616](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/616) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sidebar to new frontend. PR [#587](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/587) by [@alejsdev](https://github.com/alejsdev). From 123a0fa547bb97ded4d64f3abd4f85421c502896 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:53:29 +0000 Subject: [PATCH 189/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 90bc09f88c..a513067570 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Migrate to RouterProvider and other refactors . PR [#598](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/598) by [@alejsdev](https://github.com/alejsdev). * ✨ Add delete_user; refactor delete_item. PR [#594](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/594) by [@alejsdev](https://github.com/alejsdev). * ✨ Add state store to new frontend. PR [#592](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/592) by [@alejsdev](https://github.com/alejsdev). * ✨ Add form validation to Admin, Items and Login. PR [#616](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/616) by [@alejsdev](https://github.com/alejsdev). From 194693b5a0b528395f98db7eedd61c9d935b08fa Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:53:37 +0000 Subject: [PATCH 190/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a513067570..65888ee80c 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add dark mode to new-frontend and conditional sidebar items. PR [#600](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/600) by [@alejsdev](https://github.com/alejsdev). * ✨ Migrate to RouterProvider and other refactors . PR [#598](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/598) by [@alejsdev](https://github.com/alejsdev). * ✨ Add delete_user; refactor delete_item. PR [#594](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/594) by [@alejsdev](https://github.com/alejsdev). * ✨ Add state store to new frontend. PR [#592](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/592) by [@alejsdev](https://github.com/alejsdev). From 664efd54bd0a0a9ed3735c1806e4d4b50b0200bc Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:53:41 +0000 Subject: [PATCH 191/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 65888ee80c..8f216433a0 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add support for updating items and upgrade SQLModel to 0.0.16 (which supports model object updates). PR [#601](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/601) by [@tiangolo](https://github.com/tiangolo). * ✨ Add dark mode to new-frontend and conditional sidebar items. PR [#600](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/600) by [@alejsdev](https://github.com/alejsdev). * ✨ Migrate to RouterProvider and other refactors . PR [#598](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/598) by [@alejsdev](https://github.com/alejsdev). * ✨ Add delete_user; refactor delete_item. PR [#594](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/594) by [@alejsdev](https://github.com/alejsdev). From 490030ad50cb219c0ec7b0aa3c8c060880dba7c9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:53:43 +0000 Subject: [PATCH 192/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8f216433a0..a0c989fc74 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* ♻️ Refactor update endpoints and regenerate client for new-frontend. PR [#602](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/602) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Layout to App. PR [#588](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/588) by [@alejsdev](https://github.com/alejsdev). * ♻️ Re-enable user update path operations for frontend client generation. PR [#574](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/574) by [@alejsdev](https://github.com/alejsdev). * ♻️ Remove type ignores and add `response_model`. PR [#572](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/572) by [@alejsdev](https://github.com/alejsdev). From f59be2fc60c4dc5609b7eae1e865bd1cf64b3cb1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:53:56 +0000 Subject: [PATCH 193/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a0c989fc74..2cbee0ec75 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ♻ Refactor items and services endpoints to return count and data, and add CI tests. PR [#599](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/599) by [@estebanx64](https://github.com/estebanx64). * ✨ Add support for updating items and upgrade SQLModel to 0.0.16 (which supports model object updates). PR [#601](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/601) by [@tiangolo](https://github.com/tiangolo). * ✨ Add dark mode to new-frontend and conditional sidebar items. PR [#600](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/600) by [@alejsdev](https://github.com/alejsdev). * ✨ Migrate to RouterProvider and other refactors . PR [#598](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/598) by [@alejsdev](https://github.com/alejsdev). From 4e3ab90b63ae9fea260a9c27dfdf4f362975bcc7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:54:08 +0000 Subject: [PATCH 194/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2cbee0ec75..5b6ebedba4 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* ♻ Re-structure Docker Compose files, discard Docker Swarm specific logic. PR [#607](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/607) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor update endpoints and regenerate client for new-frontend. PR [#602](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/602) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Layout to App. PR [#588](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/588) by [@alejsdev](https://github.com/alejsdev). * ♻️ Re-enable user update path operations for frontend client generation. PR [#574](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/574) by [@alejsdev](https://github.com/alejsdev). From 117028d2cd5fbee2c847a938b301bc266e99b45d Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:54:13 +0000 Subject: [PATCH 195/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5b6ebedba4..c2cab0f755 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* 🔥 Clean up old files no longer relevant. PR [#608](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/608) by [@tiangolo](https://github.com/tiangolo). * ♻ Re-structure Docker Compose files, discard Docker Swarm specific logic. PR [#607](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/607) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor update endpoints and regenerate client for new-frontend. PR [#602](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/602) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Layout to App. PR [#588](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/588) by [@alejsdev](https://github.com/alejsdev). From 0cda0f797c99a8e724adae8ee8f1f43e644dd3b3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:54:19 +0000 Subject: [PATCH 196/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c2cab0f755..6af92f673e 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* 🚚 Refactor and simplify backend file structure. PR [#609](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/609) by [@tiangolo](https://github.com/tiangolo). * 🔥 Clean up old files no longer relevant. PR [#608](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/608) by [@tiangolo](https://github.com/tiangolo). * ♻ Re-structure Docker Compose files, discard Docker Swarm specific logic. PR [#607](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/607) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor update endpoints and regenerate client for new-frontend. PR [#602](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/602) by [@alejsdev](https://github.com/alejsdev). From 397578acd0d2e936c8f481787619f5eb1437cfed Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:56:13 +0000 Subject: [PATCH 197/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6af92f673e..c3aded189b 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ➕ Replace black, isort, flake8, autoflake with ruff and upgrade mypy. PR [#610](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/610) by [@tiangolo](https://github.com/tiangolo). * ♻ Refactor items and services endpoints to return count and data, and add CI tests. PR [#599](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/599) by [@estebanx64](https://github.com/estebanx64). * ✨ Add support for updating items and upgrade SQLModel to 0.0.16 (which supports model object updates). PR [#601](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/601) by [@tiangolo](https://github.com/tiangolo). * ✨ Add dark mode to new-frontend and conditional sidebar items. PR [#600](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/600) by [@alejsdev](https://github.com/alejsdev). From f67fd8f704c2f487709a09bd84c6b42fb71eb28a Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:56:18 +0000 Subject: [PATCH 198/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c3aded189b..f819c77fc0 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* 🎨 Format files with pre-commit and Ruff. PR [#611](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/611) by [@tiangolo](https://github.com/tiangolo). * 🚚 Refactor and simplify backend file structure. PR [#609](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/609) by [@tiangolo](https://github.com/tiangolo). * 🔥 Clean up old files no longer relevant. PR [#608](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/608) by [@tiangolo](https://github.com/tiangolo). * ♻ Re-structure Docker Compose files, discard Docker Swarm specific logic. PR [#607](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/607) by [@tiangolo](https://github.com/tiangolo). From 502c08cfb6154bfa438d22262ae7694e29bd3403 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:56:21 +0000 Subject: [PATCH 199/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f819c77fc0..c6eb5af21f 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add Copier, migrate from Cookiecutter, in a way that supports using the project as is, forking or cloning it. PR [#612](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/612) by [@tiangolo](https://github.com/tiangolo). * ➕ Replace black, isort, flake8, autoflake with ruff and upgrade mypy. PR [#610](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/610) by [@tiangolo](https://github.com/tiangolo). * ♻ Refactor items and services endpoints to return count and data, and add CI tests. PR [#599](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/599) by [@estebanx64](https://github.com/estebanx64). * ✨ Add support for updating items and upgrade SQLModel to 0.0.16 (which supports model object updates). PR [#601](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/601) by [@tiangolo](https://github.com/tiangolo). From ef1d507647b93a43022dd5eb4606a7b06eb15c3c Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:56:29 +0000 Subject: [PATCH 200/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c6eb5af21f..3626378cff 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,7 @@ After using this generator, your new project (the directory created) will contai #### Docs +* 📝 Update internal README and referred files. PR [#613](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/613) by [@tiangolo](https://github.com/tiangolo). * 📝 Update README with in construction notice. PR [#552](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/552) by [@tiangolo](https://github.com/tiangolo). * Add docs about reporting test coverage in HTML. PR [#161](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/161). * Add docs about removing the frontend, for an API-only app. PR [#156](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/156). From 9116e5621ab942f037f263d4c4177fecede021e3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:56:32 +0000 Subject: [PATCH 201/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3626378cff..c770a898cc 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Restructure folders, allow editing of users/items, and implement other refactors and improvements. PR [#603](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/603) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Copier, migrate from Cookiecutter, in a way that supports using the project as is, forking or cloning it. PR [#612](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/612) by [@tiangolo](https://github.com/tiangolo). * ➕ Replace black, isort, flake8, autoflake with ruff and upgrade mypy. PR [#610](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/610) by [@tiangolo](https://github.com/tiangolo). * ♻ Refactor items and services endpoints to return count and data, and add CI tests. PR [#599](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/599) by [@estebanx64](https://github.com/estebanx64). From 0dccdd1bad979dfbe6dbbafd540f8de6cdaa8aed Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:56:38 +0000 Subject: [PATCH 202/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c770a898cc..f8ea7392b5 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Support delete own account and other tweaks. PR [#614](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/614) by [@alejsdev](https://github.com/alejsdev). * ✨ Restructure folders, allow editing of users/items, and implement other refactors and improvements. PR [#603](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/603) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Copier, migrate from Cookiecutter, in a way that supports using the project as is, forking or cloning it. PR [#612](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/612) by [@tiangolo](https://github.com/tiangolo). * ➕ Replace black, isort, flake8, autoflake with ruff and upgrade mypy. PR [#610](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/610) by [@tiangolo](https://github.com/tiangolo). From 835521a27a014730f56456eb0c525ff501deff8c Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:56:48 +0000 Subject: [PATCH 203/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f8ea7392b5..d7b7bed6d2 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,10 @@ After using this generator, your new project (the directory created) will contai * Simplify `docker-compose.*.yml` files, refactor deployment to reduce config files. PR [#153](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/153). * Simplify env var files, merge to a single `.env` file. PR [#151](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/151). +#### Upgrades + +* ⬆ Upgrade code to support pydantic V2. PR [#615](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/615) by [@estebanx64](https://github.com/estebanx64). + #### Docs * 📝 Update internal README and referred files. PR [#613](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/613) by [@tiangolo](https://github.com/tiangolo). From a3fae621d793cf0d9664ce0abd84bb35af79ba08 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:57:02 +0000 Subject: [PATCH 204/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d7b7bed6d2..2b8ce873ed 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add new pages, components, panels, modals, and theme; refactor and improvements in existing components. PR [#593](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/593) by [@alejsdev](https://github.com/alejsdev). * ✨ Support delete own account and other tweaks. PR [#614](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/614) by [@alejsdev](https://github.com/alejsdev). * ✨ Restructure folders, allow editing of users/items, and implement other refactors and improvements. PR [#603](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/603) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Copier, migrate from Cookiecutter, in a way that supports using the project as is, forking or cloning it. PR [#612](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/612) by [@tiangolo](https://github.com/tiangolo). From 346342fb71a754319fabda75503b1b0fbc41d356 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 23:57:06 +0000 Subject: [PATCH 205/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2b8ce873ed..88fb1292a9 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ After using this generator, your new project (the directory created) will contai #### Features +* ✨ Add `Not Found` page. PR [#595](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/595) by [@alejsdev](https://github.com/alejsdev). * ✨ Add new pages, components, panels, modals, and theme; refactor and improvements in existing components. PR [#593](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/593) by [@alejsdev](https://github.com/alejsdev). * ✨ Support delete own account and other tweaks. PR [#614](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/614) by [@alejsdev](https://github.com/alejsdev). * ✨ Restructure folders, allow editing of users/items, and implement other refactors and improvements. PR [#603](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/603) by [@alejsdev](https://github.com/alejsdev). From 9c35218136f40cc5b721c3e89634eba7b7a35aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 29 Feb 2024 02:03:23 +0100 Subject: [PATCH 206/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20and=20u?= =?UTF-8?q?pdate=20CORS,=20remove=20trailing=20slash=20from=20new=20Pydant?= =?UTF-8?q?ic=20v2=20(#617)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.env | 2 +- src/backend/app/core/config.py | 14 ++++++++------ src/backend/app/main.py | 4 +++- src/docker-compose.yml | 1 - 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/.env b/src/.env index 2cf8e643e9..f63ba004d8 100644 --- a/src/.env +++ b/src/.env @@ -7,7 +7,7 @@ PROJECT_NAME="FastAPI Project" STACK_NAME=fastapi-project # Backend -BACKEND_CORS_ORIGINS="[\"http://localhost\", \"http://localhost:4200\", \"http://localhost:3000\", \"http://localhost:8080\", \"https://localhost\", \"https://localhost:4200\", \"https://localhost:3000\", \"https://localhost:8080\", \"http://local.dockertoolbox.tiangolo.com\", \"http://localhost.tiangolo.com\"]" +BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com" SECRET_KEY=changethis FIRST_SUPERUSER=admin@example.com FIRST_SUPERUSER_PASSWORD=changethis diff --git a/src/backend/app/core/config.py b/src/backend/app/core/config.py index baed6888df..5341e0ad4d 100644 --- a/src/backend/app/core/config.py +++ b/src/backend/app/core/config.py @@ -16,12 +16,11 @@ class Settings(BaseSettings): SECRET_KEY: str = secrets.token_urlsafe(32) # 60 minutes * 24 hours * 8 days = 8 days ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 - SERVER_NAME: str SERVER_HOST: AnyHttpUrl # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \ # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]' - BACKEND_CORS_ORIGINS: list[AnyHttpUrl] = [] + BACKEND_CORS_ORIGINS: list[AnyHttpUrl] | str = [] @field_validator("BACKEND_CORS_ORIGINS", mode="before") @classmethod @@ -38,7 +37,7 @@ def assemble_cors_origins(cls, v: str | list[str]) -> list[str] | str: @field_validator("SENTRY_DSN", mode="before") @classmethod def sentry_dsn_can_be_blank(cls, v: str) -> str | None: - if len(v) == 0: + if not v: return None return v @@ -65,7 +64,8 @@ def assemble_db_connection(cls, v: str | None, info: ValidationInfo) -> Any: SMTP_HOST: str | None = None SMTP_USER: str | None = None SMTP_PASSWORD: str | None = None - EMAILS_FROM_EMAIL: str | None = None #TODO: update type to EmailStr when sqlmodel supports it + # TODO: update type to EmailStr when sqlmodel supports it + EMAILS_FROM_EMAIL: str | None = None EMAILS_FROM_NAME: str | None = None @field_validator("EMAILS_FROM_NAME") @@ -86,8 +86,10 @@ def get_emails_enabled(cls, v: bool, info: ValidationInfo) -> bool: and info.data.get("EMAILS_FROM_EMAIL") ) - EMAIL_TEST_USER: str = "test@example.com" #TODO: update type to EmailStr when sqlmodel supports it - FIRST_SUPERUSER: str #TODO: update type to EmailStr when sqlmodel supports it + # TODO: update type to EmailStr when sqlmodel supports it + EMAIL_TEST_USER: str = "test@example.com" + # TODO: update type to EmailStr when sqlmodel supports it + FIRST_SUPERUSER: str FIRST_SUPERUSER_PASSWORD: str USERS_OPEN_REGISTRATION: bool = False model_config = SettingsConfigDict(case_sensitive=True) diff --git a/src/backend/app/main.py b/src/backend/app/main.py index 006345dab2..9722c944f9 100644 --- a/src/backend/app/main.py +++ b/src/backend/app/main.py @@ -20,7 +20,9 @@ def custom_generate_unique_id(route: APIRoute): if settings.BACKEND_CORS_ORIGINS: app.add_middleware( CORSMiddleware, - allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS], + allow_origins=[ + str(origin).strip("/") for origin in settings.BACKEND_CORS_ORIGINS + ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 271dbf8909..bbbe66c0b4 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -155,7 +155,6 @@ services: env_file: - .env environment: - - SERVER_NAME=${DOMAIN?Variable not set} - SERVER_HOST=https://${DOMAIN?Variable not set} # Allow explicit env var override for tests - SMTP_HOST=${SMTP_HOST?Variable not set} From 359aaaa5f6b9e22b64add234630237d4af3c8112 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 01:03:45 +0000 Subject: [PATCH 207/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 88fb1292a9..89f7be45cd 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* ♻️ Refactor and update CORS, remove trailing slash from new Pydantic v2. PR [#617](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/617) by [@tiangolo](https://github.com/tiangolo). * 🎨 Format files with pre-commit and Ruff. PR [#611](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/611) by [@tiangolo](https://github.com/tiangolo). * 🚚 Refactor and simplify backend file structure. PR [#609](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/609) by [@tiangolo](https://github.com/tiangolo). * 🔥 Clean up old files no longer relevant. PR [#608](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/608) by [@tiangolo](https://github.com/tiangolo). From 594e4d3ccbe559e7762e240dcf79cd8bafa2445b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 29 Feb 2024 02:09:24 +0100 Subject: [PATCH 208/771] =?UTF-8?q?=F0=9F=94=A7=20Update=20.env=20to=20all?= =?UTF-8?q?ow=20local=20debug=20for=20the=20backend=20(#618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.env | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/.env b/src/.env index f63ba004d8..e44fb325d0 100644 --- a/src/.env +++ b/src/.env @@ -2,6 +2,8 @@ DOMAIN=localhost # DOMAIN=localhost.tiangolo.com +SERVER_HOST=http://localhost + PROJECT_NAME="FastAPI Project" STACK_NAME=fastapi-project From 8abd8dd494ec5712910f6379a1f1a7e87023c9af Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 01:09:41 +0000 Subject: [PATCH 209/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 89f7be45cd..8bb3f11aaa 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,7 @@ After using this generator, your new project (the directory created) will contai #### Refactors +* 🔧 Update .env to allow local debug for the backend. PR [#618](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/618) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor and update CORS, remove trailing slash from new Pydantic v2. PR [#617](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/617) by [@tiangolo](https://github.com/tiangolo). * 🎨 Format files with pre-commit and Ruff. PR [#611](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/611) by [@tiangolo](https://github.com/tiangolo). * 🚚 Refactor and simplify backend file structure. PR [#609](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/609) by [@tiangolo](https://github.com/tiangolo). From ae634c686b3f32cdd5b35e5bf4c6b60b3f0c8382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 29 Feb 2024 14:00:15 +0100 Subject: [PATCH 210/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20GitHub=20Action?= =?UTF-8?q?=20latest-changes=20and=20move=20release=20notes=20to=20indepen?= =?UTF-8?q?dent=20file=20(#619)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/latest-changes.yml | 12 +- README.md | 197 +-------------------------- release-notes.md | 194 ++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 195 deletions(-) create mode 100644 release-notes.md diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index e91f4e6b3f..6ed1cb6df4 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -20,10 +20,20 @@ jobs: latest-changes: runs-on: ubuntu-latest steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 with: + # To allow latest-changes to commit to the main branch token: ${{ secrets.FULL_STACK_FASTAPI_POSTGRESQL_LATEST_CHANGES }} - - uses: docker://tiangolo/latest-changes:0.2.0 + - uses: docker://tiangolo/latest-changes:0.3.0 # - uses: tiangolo/latest-changes@main with: token: ${{ secrets.GITHUB_TOKEN }} + latest_changes_file: ./release-notes.md + latest_changes_header: '## Latest Changes' + end_regex: '^## ' + debug_logs: true + label_header_prefix: '### ' diff --git a/README.md b/README.md index 8bb3f11aaa..71a6aa360a 100644 --- a/README.md +++ b/README.md @@ -121,11 +121,11 @@ The input variables, with their default values (some auto generated) are: * `smtp_user`: The user to use in the SMTP connection. The value will be given by your email provider. * `smtp_password`: The password to be used in the SMTP connection. The value will be given by the email provider. * `smtp_emails_from_email`: The email account to use as the sender in the notification emails, it would be something like `info@your-custom-domain.com`. - + * `postgres_password`: Postgres database password. Use the method above to generate it. (You could easily modify it to use MySQL, MariaDB, etc). * `pgadmin_default_user`: PGAdmin default user, to log-in to the PGAdmin interface. * `pgadmin_default_user_password`: PGAdmin default user password. Generate it with the method above. - + * `traefik_constraint_tag`: The tag to be used by the internal Traefik load balancer (for example, to divide requests between backend and frontend) for production. Used to separate this stack from any other stack you might have. This should identify each stack in each environment (production, staging, etc). * `traefik_constraint_tag_staging`: The Traefik tag to be used while on staging. * `traefik_public_constraint_tag`: The tag that should be used by stack services that should communicate with the public. @@ -155,198 +155,7 @@ After using this generator, your new project (the directory created) will contai ## Release Notes -### Latest Changes - -* ⬆ Bump tiangolo/issue-manager from 0.2.0 to 0.5.0. PR [#591](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/591) by [@dependabot[bot]](https://github.com/apps/dependabot). -* ✨ Upgrade items router with new SQLModel models, simplified logic, and new FastAPI Annotated dependencies. PR [#560](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/560) by [@tiangolo](https://github.com/tiangolo). -* ✨ Adopt SQLModel, create models, start using it. PR [#559](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/559) by [@tiangolo](https://github.com/tiangolo). -* ⬆️ Upgrade Python version and dependencies. PR [#558](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/558) by [@tiangolo](https://github.com/tiangolo). -* 🔧 Add missing dotenv variables. PR [#554](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/554) by [@tiangolo](https://github.com/tiangolo). - -#### Features - -* ✨ Add `Not Found` page. PR [#595](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/595) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add new pages, components, panels, modals, and theme; refactor and improvements in existing components. PR [#593](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/593) by [@alejsdev](https://github.com/alejsdev). -* ✨ Support delete own account and other tweaks. PR [#614](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/614) by [@alejsdev](https://github.com/alejsdev). -* ✨ Restructure folders, allow editing of users/items, and implement other refactors and improvements. PR [#603](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/603) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add Copier, migrate from Cookiecutter, in a way that supports using the project as is, forking or cloning it. PR [#612](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/612) by [@tiangolo](https://github.com/tiangolo). -* ➕ Replace black, isort, flake8, autoflake with ruff and upgrade mypy. PR [#610](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/610) by [@tiangolo](https://github.com/tiangolo). -* ♻ Refactor items and services endpoints to return count and data, and add CI tests. PR [#599](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/599) by [@estebanx64](https://github.com/estebanx64). -* ✨ Add support for updating items and upgrade SQLModel to 0.0.16 (which supports model object updates). PR [#601](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/601) by [@tiangolo](https://github.com/tiangolo). -* ✨ Add dark mode to new-frontend and conditional sidebar items. PR [#600](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/600) by [@alejsdev](https://github.com/alejsdev). -* ✨ Migrate to RouterProvider and other refactors . PR [#598](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/598) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add delete_user; refactor delete_item. PR [#594](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/594) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add state store to new frontend. PR [#592](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/592) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add form validation to Admin, Items and Login. PR [#616](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/616) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add Sidebar to new frontend. PR [#587](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/587) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add Login to new frontend. PR [#585](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/585) by [@alejsdev](https://github.com/alejsdev). -* ✨ Include schemas in generated frontend client. PR [#584](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/584) by [@alejsdev](https://github.com/alejsdev). -* ✨ Regenerate frontend client with recent changes. PR [#575](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/575) by [@alejsdev](https://github.com/alejsdev). -* ♻️ Refactor API in `utils.py`. PR [#573](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/573) by [@alejsdev](https://github.com/alejsdev). -* ✨ Update code for login API. PR [#571](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/571) by [@tiangolo](https://github.com/tiangolo). -* ✨ Add client in frontend and client generation. PR [#569](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/569) by [@alejsdev](https://github.com/alejsdev). -* 🐳 Set up Docker config for new-frontend. PR [#564](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/564) by [@alejsdev](https://github.com/alejsdev). -* ✨ Set up new frontend with Vite, TypeScript and React. PR [#563](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/563) by [@alejsdev](https://github.com/alejsdev). -* 📌 Add NodeJS version management and instructions. PR [#551](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/551) by [@alejsdev](https://github.com/alejsdev). -* Add consistent errors for env vars not set. PR [#200](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/200). -* Upgrade Traefik to version 2, keeping in sync with DockerSwarm.rocks. PR [#199](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/199). -* Run tests with `TestClient`. PR [#160](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/160). - -#### Fixes - -* 🐛 Add `onClose` to `SidebarItems`. PR [#589](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/589) by [@alejsdev](https://github.com/alejsdev). -* 🐛 Fix positional argument bug in `init_db.py`. PR [#562](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/562) by [@alejsdev](https://github.com/alejsdev). -* 📌 Fix flower Docker image, pin version. PR [#396](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/396) by [@sanggusti](https://github.com/sanggusti). -* 🐛 Fix Celery worker command. PR [#443](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/443) by [@bechtold](https://github.com/bechtold). -* 🐛 Fix Poetry installation in Dockerfile and upgrade Python version and packages to fix Docker build. PR [#480](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/480) by [@little7Li](https://github.com/little7Li). - -#### Refactors - -* 🔧 Update .env to allow local debug for the backend. PR [#618](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/618) by [@tiangolo](https://github.com/tiangolo). -* ♻️ Refactor and update CORS, remove trailing slash from new Pydantic v2. PR [#617](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/617) by [@tiangolo](https://github.com/tiangolo). -* 🎨 Format files with pre-commit and Ruff. PR [#611](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/611) by [@tiangolo](https://github.com/tiangolo). -* 🚚 Refactor and simplify backend file structure. PR [#609](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/609) by [@tiangolo](https://github.com/tiangolo). -* 🔥 Clean up old files no longer relevant. PR [#608](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/608) by [@tiangolo](https://github.com/tiangolo). -* ♻ Re-structure Docker Compose files, discard Docker Swarm specific logic. PR [#607](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/607) by [@tiangolo](https://github.com/tiangolo). -* ♻️ Refactor update endpoints and regenerate client for new-frontend. PR [#602](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/602) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add Layout to App. PR [#588](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/588) by [@alejsdev](https://github.com/alejsdev). -* ♻️ Re-enable user update path operations for frontend client generation. PR [#574](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/574) by [@alejsdev](https://github.com/alejsdev). -* ♻️ Remove type ignores and add `response_model`. PR [#572](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/572) by [@alejsdev](https://github.com/alejsdev). -* ♻️ Refactor Users API and dependencies. PR [#561](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/561) by [@alejsdev](https://github.com/alejsdev). -* ♻️ Refactor frontend Docker build setup, use plain NodeJS, use custom Nginx config, fix build for old Vue. PR [#555](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/555) by [@tiangolo](https://github.com/tiangolo). -* ♻️ Refactor project generation, discard cookiecutter, use plain git/clone/fork. PR [#553](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/553) by [@tiangolo](https://github.com/tiangolo). -* Refactor backend: - * Simplify configs for tools and format to better support editor integration. - * Add mypy configurations and plugins. - * Add types to all the codebase. - * Update types for SQLAlchemy models with plugin. - * Update and refactor CRUD utils. - * Refactor DB sessions to use dependencies with `yield`. - * Refactor dependencies, security, CRUD, models, schemas, etc. To simplify code and improve autocompletion. - * Change from PyJWT to Python-JOSE as it supports additional use cases. - * Fix JWT tokens using user email/ID as the subject in `sub`. - * PR [#158](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/158). -* Simplify `docker-compose.*.yml` files, refactor deployment to reduce config files. PR [#153](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/153). -* Simplify env var files, merge to a single `.env` file. PR [#151](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/151). - -#### Upgrades - -* ⬆ Upgrade code to support pydantic V2. PR [#615](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/615) by [@estebanx64](https://github.com/estebanx64). - -#### Docs - -* 📝 Update internal README and referred files. PR [#613](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/613) by [@tiangolo](https://github.com/tiangolo). -* 📝 Update README with in construction notice. PR [#552](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/552) by [@tiangolo](https://github.com/tiangolo). -* Add docs about reporting test coverage in HTML. PR [#161](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/161). -* Add docs about removing the frontend, for an API-only app. PR [#156](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/156). - -#### Internal - -* 👷 Add dependabot. PR [#547](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/547) by [@tiangolo](https://github.com/tiangolo). -* 👷 Fix latest-changes GitHub Action token, strike 2. PR [#546](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/546) by [@tiangolo](https://github.com/tiangolo). -* 👷 Fix latest-changes GitHub Action token config. PR [#545](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/545) by [@tiangolo](https://github.com/tiangolo). -* 👷 Add latest-changes GitHub Action. PR [#544](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/544) by [@tiangolo](https://github.com/tiangolo). -* Update issue-manager. PR [#211](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/211). -* Add [GitHub Sponsors](https://github.com/sponsors/tiangolo) button. PR [#201](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/201). -* Simplify scripts and development, update docs and configs. PR [#155](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/155). - -### 0.5.0 - -* Make the Traefik public network a fixed default of `traefik-public` as done in DockerSwarm.rocks, to simplify development and iteration of the project generator. PR [#150](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/150). -* Update to PostgreSQL 12. PR [#148](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/148). by [@RCheese](https://github.com/RCheese). -* Use Poetry for package management. Initial PR [#144](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/144) by [@RCheese](https://github.com/RCheese). -* Fix Windows line endings for shell scripts after project generation with Cookiecutter hooks. PR [#149](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/149). -* Upgrade Vue CLI to version 4. PR [#120](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/120) by [@br3ndonland](https://github.com/br3ndonland). -* Remove duplicate `login` tag. PR [#135](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/135) by [@Nonameentered](https://github.com/Nonameentered). -* Fix showing email in dashboard when there's no user's full name. PR [#129](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/129) by [@rlonka](https://github.com/rlonka). -* Format code with Black and Flake8. PR [#121](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/121) by [@br3ndonland](https://github.com/br3ndonland). -* Simplify SQLAlchemy Base class. PR [#117](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/117) by [@airibarne](https://github.com/airibarne). -* Update CRUD utils for users, handling password hashing. PR [#106](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/106) by [@mocsar](https://github.com/mocsar). -* Use `.` instead of `source` for interoperability. PR [#98](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/98) by [@gucharbon](https://github.com/gucharbon). -* Use Pydantic's `BaseSettings` for settings/configs and env vars. PR [#87](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/87) by [@StephenBrown2](https://github.com/StephenBrown2). -* Remove `package-lock.json` to let everyone lock their own versions (depending on OS, etc). -* Simplify Traefik service labels PR [#139](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/139). -* Add email validation. PR [#40](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/40) by [@kedod](https://github.com/kedod). -* Fix typo in README. PR [#83](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/83) by [@ashears](https://github.com/ashears). -* Fix typo in README. PR [#80](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/80) by [@abjoker](https://github.com/abjoker). -* Fix function name `read_item` and response code. PR [#74](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/74) by [@jcaguirre89](https://github.com/jcaguirre89). -* Fix typo in comment. PR [#70](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/70) by [@daniel-butler](https://github.com/daniel-butler). -* Fix Flower Docker configuration. PR [#37](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/37) by [@dmontagu](https://github.com/dmontagu). -* Add new CRUD utils based on DB and Pydantic models. Initial PR [#23](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/23) by [@ebreton](https://github.com/ebreton). -* Add normal user testing Pytest fixture. PR [#20](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/20) by [@ebreton](https://github.com/ebreton). - -### 0.4.0 - -* Fix security on resetting a password. Receive token as body, not query. PR [#34](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/34). - -* Fix security on resetting a password. Receive it as body, not query. PR [#33](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/33) by [@dmontagu](https://github.com/dmontagu). - -* Fix SQLAlchemy class lookup on initialization. PR [#29](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/29) by [@ebreton](https://github.com/ebreton). - -* Fix SQLAlchemy operation errors on database restart. PR [#32](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/32) by [@ebreton](https://github.com/ebreton). - -* Fix locations of scripts in generated README. PR [#19](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/19) by [@ebreton](https://github.com/ebreton). - -* Forward arguments from script to `pytest` inside container. PR [#17](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/17) by [@ebreton](https://github.com/ebreton). - -* Update development scripts. - -* Read Alembic configs from env vars. PR #9 by @ebreton. - -* Create DB Item objects from all Pydantic model's fields. - -* Update Jupyter Lab installation and util script/environment variable for local development. - -### 0.3.0 - -* PR #14: - * Update CRUD utils to use types better. - * Simplify Pydantic model names, from `UserInCreate` to `UserCreate`, etc. - * Upgrade packages. - * Add new generic "Items" models, crud utils, endpoints, and tests. To facilitate re-using them to create new functionality. As they are simple and generic (not like Users), it's easier to copy-paste and adapt them to each use case. - * Update endpoints/*path operations* to simplify code and use new utilities, prefix and tags in `include_router`. - * Update testing utils. - * Update linting rules, relax vulture to reduce false positives. - * Update migrations to include new Items. - * Update project README.md with tips about how to start with backend. - -* Upgrade Python to 3.7 as Celery is now compatible too. PR #10 by @ebreton. - -### 0.2.2 - -* Fix frontend hijacking /docs in development. Using latest https://github.com/tiangolo/node-frontend with custom Nginx configs in frontend. PR #6. - -### 0.2.1 - -* Fix documentation for *path operation* to get user by ID. PR #4 by @mpclarkson in FastAPI. - -* Set `/start-reload.sh` as a command override for development by default. - -* Update generated README. - -### 0.2.0 - -**PR #2**: - -* Simplify and update backend `Dockerfile`s. -* Refactor and simplify backend code, improve naming, imports, modules and "namespaces". -* Improve and simplify Vuex integration with TypeScript accessors. -* Standardize frontend components layout, buttons order, etc. -* Add local development scripts (to develop this project generator itself). -* Add logs to startup modules to detect errors early. -* Improve FastAPI dependency utilities, to simplify and reduce code (to require a superuser). - -### 0.1.2 - -* Fix path operation to update self-user, set parameters as body payload. - -### 0.1.1 - -Several bug fixes since initial publication, including: - -* Order of path operations for users. -* Frontend sending login data in the correct format. -* Add https://localhost variants to CORS. +Check the file [release-notes.md](./release-notes.md). ## License diff --git a/release-notes.md b/release-notes.md new file mode 100644 index 0000000000..d00ae29442 --- /dev/null +++ b/release-notes.md @@ -0,0 +1,194 @@ +# Release Notes + +## Latest Changes + +* ⬆ Bump tiangolo/issue-manager from 0.2.0 to 0.5.0. PR [#591](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/591) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ✨ Upgrade items router with new SQLModel models, simplified logic, and new FastAPI Annotated dependencies. PR [#560](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/560) by [@tiangolo](https://github.com/tiangolo). +* ✨ Adopt SQLModel, create models, start using it. PR [#559](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/559) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Upgrade Python version and dependencies. PR [#558](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/558) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Add missing dotenv variables. PR [#554](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/554) by [@tiangolo](https://github.com/tiangolo). + +### Features + +* ✨ Add `Not Found` page. PR [#595](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/595) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add new pages, components, panels, modals, and theme; refactor and improvements in existing components. PR [#593](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/593) by [@alejsdev](https://github.com/alejsdev). +* ✨ Support delete own account and other tweaks. PR [#614](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/614) by [@alejsdev](https://github.com/alejsdev). +* ✨ Restructure folders, allow editing of users/items, and implement other refactors and improvements. PR [#603](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/603) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add Copier, migrate from Cookiecutter, in a way that supports using the project as is, forking or cloning it. PR [#612](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/612) by [@tiangolo](https://github.com/tiangolo). +* ➕ Replace black, isort, flake8, autoflake with ruff and upgrade mypy. PR [#610](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/610) by [@tiangolo](https://github.com/tiangolo). +* ♻ Refactor items and services endpoints to return count and data, and add CI tests. PR [#599](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/599) by [@estebanx64](https://github.com/estebanx64). +* ✨ Add support for updating items and upgrade SQLModel to 0.0.16 (which supports model object updates). PR [#601](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/601) by [@tiangolo](https://github.com/tiangolo). +* ✨ Add dark mode to new-frontend and conditional sidebar items. PR [#600](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/600) by [@alejsdev](https://github.com/alejsdev). +* ✨ Migrate to RouterProvider and other refactors . PR [#598](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/598) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add delete_user; refactor delete_item. PR [#594](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/594) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add state store to new frontend. PR [#592](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/592) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add form validation to Admin, Items and Login. PR [#616](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/616) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add Sidebar to new frontend. PR [#587](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/587) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add Login to new frontend. PR [#585](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/585) by [@alejsdev](https://github.com/alejsdev). +* ✨ Include schemas in generated frontend client. PR [#584](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/584) by [@alejsdev](https://github.com/alejsdev). +* ✨ Regenerate frontend client with recent changes. PR [#575](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/575) by [@alejsdev](https://github.com/alejsdev). +* ♻️ Refactor API in `utils.py`. PR [#573](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/573) by [@alejsdev](https://github.com/alejsdev). +* ✨ Update code for login API. PR [#571](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/571) by [@tiangolo](https://github.com/tiangolo). +* ✨ Add client in frontend and client generation. PR [#569](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/569) by [@alejsdev](https://github.com/alejsdev). +* 🐳 Set up Docker config for new-frontend. PR [#564](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/564) by [@alejsdev](https://github.com/alejsdev). +* ✨ Set up new frontend with Vite, TypeScript and React. PR [#563](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/563) by [@alejsdev](https://github.com/alejsdev). +* 📌 Add NodeJS version management and instructions. PR [#551](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/551) by [@alejsdev](https://github.com/alejsdev). +* Add consistent errors for env vars not set. PR [#200](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/200). +* Upgrade Traefik to version 2, keeping in sync with DockerSwarm.rocks. PR [#199](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/199). +* Run tests with `TestClient`. PR [#160](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/160). + +### Fixes + +* 🐛 Add `onClose` to `SidebarItems`. PR [#589](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/589) by [@alejsdev](https://github.com/alejsdev). +* 🐛 Fix positional argument bug in `init_db.py`. PR [#562](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/562) by [@alejsdev](https://github.com/alejsdev). +* 📌 Fix flower Docker image, pin version. PR [#396](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/396) by [@sanggusti](https://github.com/sanggusti). +* 🐛 Fix Celery worker command. PR [#443](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/443) by [@bechtold](https://github.com/bechtold). +* 🐛 Fix Poetry installation in Dockerfile and upgrade Python version and packages to fix Docker build. PR [#480](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/480) by [@little7Li](https://github.com/little7Li). + +### Refactors + +* 🔧 Update .env to allow local debug for the backend. PR [#618](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/618) by [@tiangolo](https://github.com/tiangolo). +* ♻️ Refactor and update CORS, remove trailing slash from new Pydantic v2. PR [#617](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/617) by [@tiangolo](https://github.com/tiangolo). +* 🎨 Format files with pre-commit and Ruff. PR [#611](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/611) by [@tiangolo](https://github.com/tiangolo). +* 🚚 Refactor and simplify backend file structure. PR [#609](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/609) by [@tiangolo](https://github.com/tiangolo). +* 🔥 Clean up old files no longer relevant. PR [#608](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/608) by [@tiangolo](https://github.com/tiangolo). +* ♻ Re-structure Docker Compose files, discard Docker Swarm specific logic. PR [#607](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/607) by [@tiangolo](https://github.com/tiangolo). +* ♻️ Refactor update endpoints and regenerate client for new-frontend. PR [#602](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/602) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add Layout to App. PR [#588](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/588) by [@alejsdev](https://github.com/alejsdev). +* ♻️ Re-enable user update path operations for frontend client generation. PR [#574](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/574) by [@alejsdev](https://github.com/alejsdev). +* ♻️ Remove type ignores and add `response_model`. PR [#572](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/572) by [@alejsdev](https://github.com/alejsdev). +* ♻️ Refactor Users API and dependencies. PR [#561](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/561) by [@alejsdev](https://github.com/alejsdev). +* ♻️ Refactor frontend Docker build setup, use plain NodeJS, use custom Nginx config, fix build for old Vue. PR [#555](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/555) by [@tiangolo](https://github.com/tiangolo). +* ♻️ Refactor project generation, discard cookiecutter, use plain git/clone/fork. PR [#553](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/553) by [@tiangolo](https://github.com/tiangolo). +* Refactor backend: + * Simplify configs for tools and format to better support editor integration. + * Add mypy configurations and plugins. + * Add types to all the codebase. + * Update types for SQLAlchemy models with plugin. + * Update and refactor CRUD utils. + * Refactor DB sessions to use dependencies with `yield`. + * Refactor dependencies, security, CRUD, models, schemas, etc. To simplify code and improve autocompletion. + * Change from PyJWT to Python-JOSE as it supports additional use cases. + * Fix JWT tokens using user email/ID as the subject in `sub`. + * PR [#158](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/158). +* Simplify `docker-compose.*.yml` files, refactor deployment to reduce config files. PR [#153](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/153). +* Simplify env var files, merge to a single `.env` file. PR [#151](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/151). + +### Upgrades + +* ⬆ Upgrade code to support pydantic V2. PR [#615](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/615) by [@estebanx64](https://github.com/estebanx64). + +### Docs + +* 📝 Update internal README and referred files. PR [#613](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/613) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update README with in construction notice. PR [#552](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/552) by [@tiangolo](https://github.com/tiangolo). +* Add docs about reporting test coverage in HTML. PR [#161](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/161). +* Add docs about removing the frontend, for an API-only app. PR [#156](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/156). + +### Internal + +* 👷 Add dependabot. PR [#547](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/547) by [@tiangolo](https://github.com/tiangolo). +* 👷 Fix latest-changes GitHub Action token, strike 2. PR [#546](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/546) by [@tiangolo](https://github.com/tiangolo). +* 👷 Fix latest-changes GitHub Action token config. PR [#545](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/545) by [@tiangolo](https://github.com/tiangolo). +* 👷 Add latest-changes GitHub Action. PR [#544](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/544) by [@tiangolo](https://github.com/tiangolo). +* Update issue-manager. PR [#211](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/211). +* Add [GitHub Sponsors](https://github.com/sponsors/tiangolo) button. PR [#201](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/201). +* Simplify scripts and development, update docs and configs. PR [#155](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/155). + +## 0.5.0 + +* Make the Traefik public network a fixed default of `traefik-public` as done in DockerSwarm.rocks, to simplify development and iteration of the project generator. PR [#150](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/150). +* Update to PostgreSQL 12. PR [#148](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/148). by [@RCheese](https://github.com/RCheese). +* Use Poetry for package management. Initial PR [#144](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/144) by [@RCheese](https://github.com/RCheese). +* Fix Windows line endings for shell scripts after project generation with Cookiecutter hooks. PR [#149](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/149). +* Upgrade Vue CLI to version 4. PR [#120](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/120) by [@br3ndonland](https://github.com/br3ndonland). +* Remove duplicate `login` tag. PR [#135](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/135) by [@Nonameentered](https://github.com/Nonameentered). +* Fix showing email in dashboard when there's no user's full name. PR [#129](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/129) by [@rlonka](https://github.com/rlonka). +* Format code with Black and Flake8. PR [#121](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/121) by [@br3ndonland](https://github.com/br3ndonland). +* Simplify SQLAlchemy Base class. PR [#117](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/117) by [@airibarne](https://github.com/airibarne). +* Update CRUD utils for users, handling password hashing. PR [#106](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/106) by [@mocsar](https://github.com/mocsar). +* Use `.` instead of `source` for interoperability. PR [#98](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/98) by [@gucharbon](https://github.com/gucharbon). +* Use Pydantic's `BaseSettings` for settings/configs and env vars. PR [#87](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/87) by [@StephenBrown2](https://github.com/StephenBrown2). +* Remove `package-lock.json` to let everyone lock their own versions (depending on OS, etc). +* Simplify Traefik service labels PR [#139](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/139). +* Add email validation. PR [#40](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/40) by [@kedod](https://github.com/kedod). +* Fix typo in README. PR [#83](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/83) by [@ashears](https://github.com/ashears). +* Fix typo in README. PR [#80](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/80) by [@abjoker](https://github.com/abjoker). +* Fix function name `read_item` and response code. PR [#74](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/74) by [@jcaguirre89](https://github.com/jcaguirre89). +* Fix typo in comment. PR [#70](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/70) by [@daniel-butler](https://github.com/daniel-butler). +* Fix Flower Docker configuration. PR [#37](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/37) by [@dmontagu](https://github.com/dmontagu). +* Add new CRUD utils based on DB and Pydantic models. Initial PR [#23](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/23) by [@ebreton](https://github.com/ebreton). +* Add normal user testing Pytest fixture. PR [#20](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/20) by [@ebreton](https://github.com/ebreton). + +## 0.4.0 + +* Fix security on resetting a password. Receive token as body, not query. PR [#34](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/34). + +* Fix security on resetting a password. Receive it as body, not query. PR [#33](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/33) by [@dmontagu](https://github.com/dmontagu). + +* Fix SQLAlchemy class lookup on initialization. PR [#29](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/29) by [@ebreton](https://github.com/ebreton). + +* Fix SQLAlchemy operation errors on database restart. PR [#32](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/32) by [@ebreton](https://github.com/ebreton). + +* Fix locations of scripts in generated README. PR [#19](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/19) by [@ebreton](https://github.com/ebreton). + +* Forward arguments from script to `pytest` inside container. PR [#17](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/17) by [@ebreton](https://github.com/ebreton). + +* Update development scripts. + +* Read Alembic configs from env vars. PR #9 by @ebreton. + +* Create DB Item objects from all Pydantic model's fields. + +* Update Jupyter Lab installation and util script/environment variable for local development. + +## 0.3.0 + +* PR #14: + * Update CRUD utils to use types better. + * Simplify Pydantic model names, from `UserInCreate` to `UserCreate`, etc. + * Upgrade packages. + * Add new generic "Items" models, crud utils, endpoints, and tests. To facilitate re-using them to create new functionality. As they are simple and generic (not like Users), it's easier to copy-paste and adapt them to each use case. + * Update endpoints/*path operations* to simplify code and use new utilities, prefix and tags in `include_router`. + * Update testing utils. + * Update linting rules, relax vulture to reduce false positives. + * Update migrations to include new Items. + * Update project README.md with tips about how to start with backend. + +* Upgrade Python to 3.7 as Celery is now compatible too. PR #10 by @ebreton. + +## 0.2.2 + +* Fix frontend hijacking /docs in development. Using latest https://github.com/tiangolo/node-frontend with custom Nginx configs in frontend. PR #6. + +## 0.2.1 + +* Fix documentation for *path operation* to get user by ID. PR #4 by @mpclarkson in FastAPI. + +* Set `/start-reload.sh` as a command override for development by default. + +* Update generated README. + +## 0.2.0 + +**PR #2**: + +* Simplify and update backend `Dockerfile`s. +* Refactor and simplify backend code, improve naming, imports, modules and "namespaces". +* Improve and simplify Vuex integration with TypeScript accessors. +* Standardize frontend components layout, buttons order, etc. +* Add local development scripts (to develop this project generator itself). +* Add logs to startup modules to detect errors early. +* Improve FastAPI dependency utilities, to simplify and reduce code (to require a superuser). + +## 0.1.2 + +* Fix path operation to update self-user, set parameters as body payload. + +## 0.1.1 + +Several bug fixes since initial publication, including: + +* Order of path operations for users. +* Frontend sending login data in the correct format. +* Add https://localhost variants to CORS. From ce719293f7a1f5e9e7c861432d4d16b3414ec191 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 13:00:32 +0000 Subject: [PATCH 211/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index d00ae29442..5bedb9c933 100644 --- a/release-notes.md +++ b/release-notes.md @@ -80,6 +80,7 @@ ### Docs +* 👷 Update GitHub Action latest-changes and move release notes to independent file. PR [#619](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/619) by [@tiangolo](https://github.com/tiangolo). * 📝 Update internal README and referred files. PR [#613](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/613) by [@tiangolo](https://github.com/tiangolo). * 📝 Update README with in construction notice. PR [#552](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/552) by [@tiangolo](https://github.com/tiangolo). * Add docs about reporting test coverage in HTML. PR [#161](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/161). From 932968af0f0f9c9d26f731e81d7b4e13ae53726a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 29 Feb 2024 14:19:11 +0100 Subject: [PATCH 212/771] =?UTF-8?q?=F0=9F=94=A7=20Add=20VS=20Code=20debug?= =?UTF-8?q?=20configs=20(#620)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..7a10ce3b45 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug FastAPI Project backend: Python Debugger", + "type": "debugpy", + "request": "launch", + "module": "uvicorn", + "args": [ + "app.main:app", + "--reload" + ], + "cwd": "${workspaceFolder}/src/backend", + "jinja": true, + "envFile": "${workspaceFolder}/src/.env", + "env": { + "POSTGRES_SERVER": "localhost" + } + }, + { + "type": "chrome", + "request": "launch", + "name": "Debug Frontend: Launch Chrome against http://localhost:5173", + "url": "http://localhost:5173", + "webRoot": "${workspaceFolder}/src/new-frontend" + }, + ] +} From 06a9d85ed793234f6f336773318e9638b475ba2f Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 13:19:32 +0000 Subject: [PATCH 213/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 5bedb9c933..0542cc6dc5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Features +* 🔧 Add VS Code debug configs. PR [#620](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/620) by [@tiangolo](https://github.com/tiangolo). * ✨ Add `Not Found` page. PR [#595](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/595) by [@alejsdev](https://github.com/alejsdev). * ✨ Add new pages, components, panels, modals, and theme; refactor and improvements in existing components. PR [#593](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/593) by [@alejsdev](https://github.com/alejsdev). * ✨ Support delete own account and other tweaks. PR [#614](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/614) by [@alejsdev](https://github.com/alejsdev). From 062d75d1c8fdd88d15ee40ca80801bb3fa784d61 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:47:13 -0500 Subject: [PATCH 214/771] =?UTF-8?q?=E2=9C=A8=20Add=20private/public=20rout?= =?UTF-8?q?ing=20(#621)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Esteban Maya Cadavid --- src/new-frontend/public/vite.svg | 1 - .../src/components/Common/ActionsMenu.tsx | 2 +- .../UserSettings/DeleteConfirmation.tsx | 2 +- src/new-frontend/src/hooks/useAuth.tsx | 15 ++++++----- src/new-frontend/src/main.tsx | 26 ++++--------------- src/new-frontend/src/pages/Layout.tsx | 13 +++++----- src/new-frontend/src/routes/private_route.tsx | 21 +++++++++++++++ src/new-frontend/src/routes/public_route.tsx | 13 ++++++++++ src/new-frontend/src/theme.tsx | 16 +++--------- 9 files changed, 59 insertions(+), 50 deletions(-) delete mode 100644 src/new-frontend/public/vite.svg create mode 100644 src/new-frontend/src/routes/private_route.tsx create mode 100644 src/new-frontend/src/routes/public_route.tsx diff --git a/src/new-frontend/public/vite.svg b/src/new-frontend/public/vite.svg deleted file mode 100644 index e7b8dfb1b2..0000000000 --- a/src/new-frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/new-frontend/src/components/Common/ActionsMenu.tsx b/src/new-frontend/src/components/Common/ActionsMenu.tsx index 267b48e2ce..e1999d023c 100644 --- a/src/new-frontend/src/components/Common/ActionsMenu.tsx +++ b/src/new-frontend/src/components/Common/ActionsMenu.tsx @@ -12,7 +12,7 @@ import Delete from './DeleteAlert'; interface ActionsMenuProps { type: string; id: number; - disabled: boolean; + disabled?: boolean; } const ActionsMenu: React.FC = ({ type, id, disabled }) => { diff --git a/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx index 6aa846a0e2..ad7bf33496 100644 --- a/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx +++ b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -47,7 +47,7 @@ const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { - All your account data will be permanently deleted. If you're sure, please click 'Confirm' to proceed. + All your account data will be permanently deleted. If you are sure, please click 'Confirm' to proceed. diff --git a/src/new-frontend/src/hooks/useAuth.tsx b/src/new-frontend/src/hooks/useAuth.tsx index 36a3ac26cd..29f1020bb5 100644 --- a/src/new-frontend/src/hooks/useAuth.tsx +++ b/src/new-frontend/src/hooks/useAuth.tsx @@ -4,19 +4,23 @@ import { useUsersStore } from '../store/users-store'; import { useItemsStore } from '../store/items-store'; import { useNavigate } from 'react-router-dom'; +const isLoggedIn = () => { + return localStorage.getItem('access_token') !== null; +}; + const useAuth = () => { - const { user, getUser, resetUser } = useUserStore(); + const { getUser, resetUser } = useUserStore(); const { resetUsers } = useUsersStore(); const { resetItems } = useItemsStore(); const navigate = useNavigate(); - const login = async (data: AccessToken) => { const response = await LoginService.loginAccessToken({ formData: data, }); localStorage.setItem('access_token', response.access_token); await getUser(); + navigate('/'); }; const logout = () => { @@ -27,11 +31,8 @@ const useAuth = () => { navigate('/login'); }; - const isLoggedIn = () => { - return user !== null; - }; - - return { login, logout, isLoggedIn }; + return { login, logout }; } +export { isLoggedIn }; export default useAuth; \ No newline at end of file diff --git a/src/new-frontend/src/main.tsx b/src/new-frontend/src/main.tsx index 146cbb5494..f49e1f3d42 100644 --- a/src/new-frontend/src/main.tsx +++ b/src/new-frontend/src/main.tsx @@ -6,14 +6,9 @@ import { createStandaloneToast } from '@chakra-ui/toast'; import { RouterProvider, createBrowserRouter } from 'react-router-dom'; import { OpenAPI } from './client'; -import Admin from './pages/Admin'; -import Dashboard from './pages/Dashboard'; -import ErrorPage from './pages/ErrorPage'; -import Items from './pages/Items'; -import Login from './pages/Login'; -import RecoverPassword from './pages/RecoverPassword'; -import Root from './pages/Root'; -import Profile from './pages/UserSettings'; +import { isLoggedIn } from './hooks/useAuth'; +import privateRoutes from './routes/private_route'; +import publicRoutes from './routes/public_route'; import theme from './theme'; @@ -23,19 +18,8 @@ OpenAPI.TOKEN = async () => { } const router = createBrowserRouter([ - { - path: '/', - element: , - errorElement: , - children: [ - { path: '/', element: }, - { path: 'items', element: }, - { path: 'admin', element: }, - { path: 'settings', element: }, - ], - }, - { path: 'login', element: , errorElement: , }, - { path: 'recover-password', element: , errorElement: , }, + isLoggedIn() ? privateRoutes() : {}, + ...publicRoutes(), ]); const { ToastContainer } = createStandaloneToast(); diff --git a/src/new-frontend/src/pages/Layout.tsx b/src/new-frontend/src/pages/Layout.tsx index db2815001e..ac35d2f777 100644 --- a/src/new-frontend/src/pages/Layout.tsx +++ b/src/new-frontend/src/pages/Layout.tsx @@ -6,18 +6,19 @@ import { Outlet } from 'react-router-dom'; import Sidebar from '../components/Common/Sidebar'; import UserMenu from '../components/Common/UserMenu'; import { useUserStore } from '../store/user-store'; +import { isLoggedIn } from '../hooks/useAuth'; const Layout: React.FC = () => { const { getUser } = useUserStore(); useEffect(() => { - const token = localStorage.getItem('access_token'); - if (token) { - (async () => { + const fetchUser = async () => { + if (isLoggedIn()) { await getUser(); - })(); - } - }, [getUser]); + } + }; + fetchUser(); + }, []); return ( diff --git a/src/new-frontend/src/routes/private_route.tsx b/src/new-frontend/src/routes/private_route.tsx new file mode 100644 index 0000000000..8fbe34f63c --- /dev/null +++ b/src/new-frontend/src/routes/private_route.tsx @@ -0,0 +1,21 @@ +import Admin from '../pages/Admin'; +import Dashboard from '../pages/Dashboard'; +import ErrorPage from '../pages/ErrorPage'; +import Items from '../pages/Items'; +import Layout from '../pages/Layout'; +import UserSettings from '../pages/UserSettings'; + +export default function privateRoutes() { + + return { + path: '/', + element: , + errorElement: , + children: [ + { path: '/', element: }, + { path: 'items', element: }, + { path: 'admin', element: }, + { path: 'settings', element: }, + ], + }; +} diff --git a/src/new-frontend/src/routes/public_route.tsx b/src/new-frontend/src/routes/public_route.tsx new file mode 100644 index 0000000000..8b4dd4c748 --- /dev/null +++ b/src/new-frontend/src/routes/public_route.tsx @@ -0,0 +1,13 @@ +import ErrorPage from '../pages/ErrorPage'; +import Login from '../pages/Login'; +import RecoverPassword from '../pages/RecoverPassword'; + +export default function publicRoutes() { + return [ + { path: '/login', element: , errorElement: }, + { path: 'recover-password', element: , errorElement: }, + // TODO: complete this + // { path: '*', element: } + ]; +} + diff --git a/src/new-frontend/src/theme.tsx b/src/new-frontend/src/theme.tsx index cc62ba12ec..b741399146 100644 --- a/src/new-frontend/src/theme.tsx +++ b/src/new-frontend/src/theme.tsx @@ -1,13 +1,12 @@ -import { extendTheme } from "@chakra-ui/react" +import { extendTheme } from '@chakra-ui/react' const theme = extendTheme({ colors: { ui: { - main: "#009688", - secondary: "#EDF2F7", + main: '#009688', + secondary: '#EDF2F7', success: '#48BB78', danger: '#E53E3E', - focus: 'red', } }, components: { @@ -22,15 +21,6 @@ const theme = extendTheme({ }, }, }, - Input: { - baseStyle: { - field: { - _focus: { - borderColor: 'ui.focus', - }, - }, - }, - }, }, }); From a7bdf0cc13ef586feac868151ae7747bedb64f10 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 14:47:35 +0000 Subject: [PATCH 215/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 0542cc6dc5..23b9af3e20 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Features +* ✨ Add private/public routing. PR [#621](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/621) by [@alejsdev](https://github.com/alejsdev). * 🔧 Add VS Code debug configs. PR [#620](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/620) by [@tiangolo](https://github.com/tiangolo). * ✨ Add `Not Found` page. PR [#595](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/595) by [@alejsdev](https://github.com/alejsdev). * ✨ Add new pages, components, panels, modals, and theme; refactor and improvements in existing components. PR [#593](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/593) by [@alejsdev](https://github.com/alejsdev). From d737ec117a4167e3d615f88c3d9cd4f44501a4db Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 29 Feb 2024 15:42:55 -0500 Subject: [PATCH 216/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20old=20C?= =?UTF-8?q?RUD=20utils=20and=20tests=20(#622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/app/api/api_v1/endpoints/users.py | 14 +---- src/backend/app/crud.py | 55 +++++++++++++++++ src/backend/app/crud/__init__.py | 37 ----------- src/backend/app/crud/base.py | 59 ------------------ src/backend/app/crud/crud_item.py | 32 ---------- src/backend/app/crud/crud_user.py | 55 ----------------- .../app/tests/api/api_v1/test_items.py | 2 +- .../app/tests/api/api_v1/test_users.py | 16 ++--- src/backend/app/tests/conftest.py | 2 +- src/backend/app/tests/crud/test_item.py | 61 ------------------- src/backend/app/tests/crud/test_user.py | 43 ++++++------- src/backend/app/tests/utils/item.py | 18 +++--- src/backend/app/tests/utils/user.py | 17 +++--- 13 files changed, 104 insertions(+), 307 deletions(-) create mode 100644 src/backend/app/crud.py delete mode 100644 src/backend/app/crud/__init__.py delete mode 100644 src/backend/app/crud/base.py delete mode 100644 src/backend/app/crud/crud_item.py delete mode 100644 src/backend/app/crud/crud_user.py delete mode 100644 src/backend/app/tests/crud/test_item.py diff --git a/src/backend/app/api/api_v1/endpoints/users.py b/src/backend/app/api/api_v1/endpoints/users.py index 77ffa32201..1c76293aed 100644 --- a/src/backend/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/api/api_v1/endpoints/users.py @@ -166,22 +166,12 @@ def update_user( Update a user. """ - db_user = session.get(User, user_id) - if not db_user: + db_user = crud.update_user(session=session, user_id=user_id, user_in=user_in) + if db_user is None: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system", ) - user_data = user_in.model_dump(exclude_unset=True) - extra_data = {} - if "password" in user_data: - password = user_data["password"] - hashed_password = get_password_hash(password) - extra_data["hashed_password"] = hashed_password - db_user.sqlmodel_update(user_data, update=extra_data) - session.add(db_user) - session.commit() - session.refresh(db_user) return db_user diff --git a/src/backend/app/crud.py b/src/backend/app/crud.py new file mode 100644 index 0000000000..ab665cb090 --- /dev/null +++ b/src/backend/app/crud.py @@ -0,0 +1,55 @@ +from typing import Any + +from sqlmodel import Session, select + +from app.core.security import get_password_hash, verify_password +from app.models import Item, ItemCreate, User, UserCreate, UserUpdate + + +def create_user(*, session: Session, user_create: UserCreate) -> User: + db_obj = User.model_validate( + user_create, update={"hashed_password": get_password_hash(user_create.password)} + ) + session.add(db_obj) + session.commit() + session.refresh(db_obj) + return db_obj + + +def update_user(*, session: Session, user_id: int, user_in: UserUpdate) -> Any: + db_user = session.get(User, user_id) + if not db_user: + return None + user_data = user_in.model_dump(exclude_unset=True) + extra_data = {} + if "password" in user_data: + password = user_data["password"] + hashed_password = get_password_hash(password) + extra_data["hashed_password"] = hashed_password + db_user.sqlmodel_update(user_data, update=extra_data) + session.add(db_user) + session.commit() + session.refresh(db_user) + return db_user + + +def get_user_by_email(*, session: Session, email: str) -> User | None: + statement = select(User).where(User.email == email) + session_user = session.exec(statement).first() + return session_user + + +def authenticate(*, session: Session, email: str, password: str) -> User | None: + db_user = get_user_by_email(session=session, email=email) + if not db_user: + return None + if not verify_password(password, db_user.hashed_password): + return None + return db_user + +def create_item(*, session: Session, item_in: ItemCreate, owner_id: int) -> Item: + db_item = Item.model_validate(item_in, update={"owner_id": owner_id}) + session.add(db_item) + session.commit() + session.refresh(db_item) + return db_item diff --git a/src/backend/app/crud/__init__.py b/src/backend/app/crud/__init__.py deleted file mode 100644 index ee949e562d..0000000000 --- a/src/backend/app/crud/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -# For a new basic set of CRUD operations you could just do -# from .base import CRUDBase -# from app.models.item import Item -# from app.schemas.item import ItemCreate, ItemUpdate -# item = CRUDBase[Item, ItemCreate, ItemUpdate](Item) -from sqlmodel import Session, select - -from app.core.security import get_password_hash, verify_password -from app.models import User, UserCreate - -from .crud_item import item as item -from .crud_user import user as user - - -def create_user(*, session: Session, user_create: UserCreate) -> User: - db_obj = User.from_orm( - user_create, update={"hashed_password": get_password_hash(user_create.password)} - ) - session.add(db_obj) - session.commit() - session.refresh(db_obj) - return db_obj - - -def get_user_by_email(*, session: Session, email: str) -> User | None: - statement = select(User).where(User.email == email) - session_user = session.exec(statement).first() - return session_user - - -def authenticate(*, session: Session, email: str, password: str) -> User | None: - db_user = get_user_by_email(session=session, email=email) - if not db_user: - return None - if not verify_password(password, db_user.hashed_password): - return None - return db_user diff --git a/src/backend/app/crud/base.py b/src/backend/app/crud/base.py deleted file mode 100644 index 6556e3ca74..0000000000 --- a/src/backend/app/crud/base.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Any, Generic, TypeVar - -from fastapi.encoders import jsonable_encoder -from pydantic import BaseModel -from sqlalchemy.orm import Session - -ModelType = TypeVar("ModelType", bound=Any) -CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) -UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) - - -class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): - def __init__(self, model: type[ModelType]): - """ - CRUD object with default methods to Create, Read, Update, Delete (CRUD). - - **Parameters** - - * `model`: A SQLAlchemy model class - * `schema`: A Pydantic model (schema) class - """ - self.model = model - - def get(self, db: Session, id: Any) -> ModelType | None: - return db.query(self.model).filter(self.model.id == id).first() - - def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: - obj_in_data = jsonable_encoder(obj_in) - db_obj = self.model(**obj_in_data) # type: ignore - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - def update( - self, - db: Session, - *, - db_obj: ModelType, - obj_in: UpdateSchemaType | dict[str, Any], - ) -> ModelType: - obj_data = jsonable_encoder(db_obj) - if isinstance(obj_in, dict): - update_data = obj_in - else: - update_data = obj_in.dict(exclude_unset=True) - for field in obj_data: - if field in update_data: - setattr(db_obj, field, update_data[field]) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - def remove(self, db: Session, *, id: int) -> ModelType: - obj = db.query(self.model).get(id) - db.delete(obj) - db.commit() - return obj diff --git a/src/backend/app/crud/crud_item.py b/src/backend/app/crud/crud_item.py deleted file mode 100644 index 02c5060b1b..0000000000 --- a/src/backend/app/crud/crud_item.py +++ /dev/null @@ -1,32 +0,0 @@ -from fastapi.encoders import jsonable_encoder -from sqlalchemy.orm import Session - -from app.crud.base import CRUDBase -from app.models import Item -from app.schemas.item import ItemCreate, ItemUpdate - - -class CRUDItem(CRUDBase[Item, ItemCreate, ItemUpdate]): - def create_with_owner( - self, db: Session, *, obj_in: ItemCreate, owner_id: int - ) -> Item: - obj_in_data = jsonable_encoder(obj_in) - db_obj = self.model(**obj_in_data, owner_id=owner_id) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - def get_multi_by_owner( - self, db: Session, *, owner_id: int, skip: int = 0, limit: int = 100 - ) -> list[Item]: - return ( - db.query(self.model) - .filter(Item.owner_id == owner_id) - .offset(skip) - .limit(limit) - .all() - ) - - -item = CRUDItem(Item) diff --git a/src/backend/app/crud/crud_user.py b/src/backend/app/crud/crud_user.py deleted file mode 100644 index b444502db5..0000000000 --- a/src/backend/app/crud/crud_user.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Any - -from sqlalchemy.orm import Session - -from app.core.security import get_password_hash, verify_password -from app.crud.base import CRUDBase -from app.models import User -from app.schemas.user import UserCreate, UserUpdate - - -class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): - def get_by_email(self, db: Session, *, email: str) -> User | None: - return db.query(User).filter(User.email == email).first() - - def create(self, db: Session, *, obj_in: UserCreate) -> User: - db_obj = User( - email=obj_in.email, - hashed_password=get_password_hash(obj_in.password), - full_name=obj_in.full_name, - is_superuser=obj_in.is_superuser, - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - def update( - self, db: Session, *, db_obj: User, obj_in: UserUpdate | dict[str, Any] - ) -> User: - if isinstance(obj_in, dict): - update_data = obj_in - else: - update_data = obj_in.dict(exclude_unset=True) - if update_data["password"]: - hashed_password = get_password_hash(update_data["password"]) - del update_data["password"] - update_data["hashed_password"] = hashed_password - return super().update(db, db_obj=db_obj, obj_in=update_data) - - def authenticate(self, db: Session, *, email: str, password: str) -> User | None: - user = self.get_by_email(db, email=email) - if not user: - return None - if not verify_password(password, user.hashed_password): - return None - return user - - def is_active(self, user: User) -> bool: - return user.is_active - - def is_superuser(self, user: User) -> bool: - return user.is_superuser - - -user = CRUDUser(User) diff --git a/src/backend/app/tests/api/api_v1/test_items.py b/src/backend/app/tests/api/api_v1/test_items.py index f9abdcb102..0c92dd9c01 100644 --- a/src/backend/app/tests/api/api_v1/test_items.py +++ b/src/backend/app/tests/api/api_v1/test_items.py @@ -1,5 +1,5 @@ from fastapi.testclient import TestClient -from sqlalchemy.orm import Session +from sqlmodel import Session from app.core.config import settings from app.tests.utils.item import create_random_item diff --git a/src/backend/app/tests/api/api_v1/test_users.py b/src/backend/app/tests/api/api_v1/test_users.py index adc97e0e0a..17d5465798 100644 --- a/src/backend/app/tests/api/api_v1/test_users.py +++ b/src/backend/app/tests/api/api_v1/test_users.py @@ -1,9 +1,9 @@ from fastapi.testclient import TestClient -from sqlalchemy.orm import Session +from sqlmodel import Session from app import crud from app.core.config import settings -from app.schemas.user import UserCreate +from app.models import UserCreate from app.tests.utils.utils import random_email, random_lower_string @@ -42,7 +42,7 @@ def test_create_user_new_email( ) assert 200 <= r.status_code < 300 created_user = r.json() - user = crud.user.get_by_email(db, email=username) + user = crud.get_user_by_email(session=db, email=username) assert user assert user.email == created_user["email"] @@ -53,7 +53,7 @@ def test_get_existing_user( username = random_email() password = random_lower_string() user_in = UserCreate(email=username, password=password) - user = crud.user.create(db, obj_in=user_in) + user = crud.create_user(session=db, user_create=user_in) user_id = user.id r = client.get( f"{settings.API_V1_STR}/users/{user_id}", @@ -61,7 +61,7 @@ def test_get_existing_user( ) assert 200 <= r.status_code < 300 api_user = r.json() - existing_user = crud.user.get_by_email(db, email=username) + existing_user = crud.get_user_by_email(session=db, email=username) assert existing_user assert existing_user.email == api_user["email"] @@ -73,7 +73,7 @@ def test_create_user_existing_username( # username = email password = random_lower_string() user_in = UserCreate(email=username, password=password) - crud.user.create(db, obj_in=user_in) + crud.create_user(session=db, user_create=user_in) data = {"email": username, "password": password} r = client.post( f"{settings.API_V1_STR}/users/", @@ -105,12 +105,12 @@ def test_retrieve_users( username = random_email() password = random_lower_string() user_in = UserCreate(email=username, password=password) - crud.user.create(db, obj_in=user_in) + crud.create_user(session=db, user_create=user_in) username2 = random_email() password2 = random_lower_string() user_in2 = UserCreate(email=username2, password=password2) - crud.user.create(db, obj_in=user_in2) + crud.create_user(session=db, user_create=user_in2) r = client.get(f"{settings.API_V1_STR}/users/", headers=superuser_token_headers) all_users = r.json() diff --git a/src/backend/app/tests/conftest.py b/src/backend/app/tests/conftest.py index 0c76fa98c9..ffd097dc8c 100644 --- a/src/backend/app/tests/conftest.py +++ b/src/backend/app/tests/conftest.py @@ -2,7 +2,7 @@ import pytest from fastapi.testclient import TestClient -from sqlalchemy.orm import Session +from sqlmodel import Session from app.core.config import settings from app.db.engine import engine diff --git a/src/backend/app/tests/crud/test_item.py b/src/backend/app/tests/crud/test_item.py deleted file mode 100644 index e529144ef6..0000000000 --- a/src/backend/app/tests/crud/test_item.py +++ /dev/null @@ -1,61 +0,0 @@ -from sqlalchemy.orm import Session - -from app import crud -from app.schemas.item import ItemCreate, ItemUpdate -from app.tests.utils.user import create_random_user -from app.tests.utils.utils import random_lower_string - - -def test_create_item(db: Session) -> None: - title = random_lower_string() - description = random_lower_string() - item_in = ItemCreate(title=title, description=description) - user = create_random_user(db) - item = crud.item.create_with_owner(db=db, obj_in=item_in, owner_id=user.id) - assert item.title == title - assert item.description == description - assert item.owner_id == user.id - - -def test_get_item(db: Session) -> None: - title = random_lower_string() - description = random_lower_string() - item_in = ItemCreate(title=title, description=description) - user = create_random_user(db) - item = crud.item.create_with_owner(db=db, obj_in=item_in, owner_id=user.id) - stored_item = crud.item.get(db=db, id=item.id) - assert stored_item - assert item.id == stored_item.id - assert item.title == stored_item.title - assert item.description == stored_item.description - assert item.owner_id == stored_item.owner_id - - -def test_update_item(db: Session) -> None: - title = random_lower_string() - description = random_lower_string() - item_in = ItemCreate(title=title, description=description) - user = create_random_user(db) - item = crud.item.create_with_owner(db=db, obj_in=item_in, owner_id=user.id) - description2 = random_lower_string() - item_update = ItemUpdate(description=description2) - item2 = crud.item.update(db=db, db_obj=item, obj_in=item_update) - assert item.id == item2.id - assert item.title == item2.title - assert item2.description == description2 - assert item.owner_id == item2.owner_id - - -def test_delete_item(db: Session) -> None: - title = random_lower_string() - description = random_lower_string() - item_in = ItemCreate(title=title, description=description) - user = create_random_user(db) - item = crud.item.create_with_owner(db=db, obj_in=item_in, owner_id=user.id) - item2 = crud.item.remove(db=db, id=item.id) - item3 = crud.item.get(db=db, id=item.id) - assert item3 is None - assert item2.id == item.id - assert item2.title == title - assert item2.description == description - assert item2.owner_id == user.id diff --git a/src/backend/app/tests/crud/test_user.py b/src/backend/app/tests/crud/test_user.py index 2caee5b870..dcac8f0ccd 100644 --- a/src/backend/app/tests/crud/test_user.py +++ b/src/backend/app/tests/crud/test_user.py @@ -1,9 +1,9 @@ from fastapi.encoders import jsonable_encoder -from sqlalchemy.orm import Session +from sqlmodel import Session from app import crud from app.core.security import verify_password -from app.schemas.user import UserCreate, UserUpdate +from app.models import User, UserCreate, UserUpdate from app.tests.utils.utils import random_email, random_lower_string @@ -11,7 +11,7 @@ def test_create_user(db: Session) -> None: email = random_email() password = random_lower_string() user_in = UserCreate(email=email, password=password) - user = crud.user.create(db, obj_in=user_in) + user = crud.create_user(session=db, user_create=user_in) assert user.email == email assert hasattr(user, "hashed_password") @@ -20,8 +20,8 @@ def test_authenticate_user(db: Session) -> None: email = random_email() password = random_lower_string() user_in = UserCreate(email=email, password=password) - user = crud.user.create(db, obj_in=user_in) - authenticated_user = crud.user.authenticate(db, email=email, password=password) + user = crud.create_user(session=db, user_create=user_in) + authenticated_user = crud.authenticate(session=db, email=email, password=password) assert authenticated_user assert user.email == authenticated_user.email @@ -29,7 +29,7 @@ def test_authenticate_user(db: Session) -> None: def test_not_authenticate_user(db: Session) -> None: email = random_email() password = random_lower_string() - user = crud.user.authenticate(db, email=email, password=password) + user = crud.authenticate(session=db, email=email, password=password) assert user is None @@ -37,44 +37,40 @@ def test_check_if_user_is_active(db: Session) -> None: email = random_email() password = random_lower_string() user_in = UserCreate(email=email, password=password) - user = crud.user.create(db, obj_in=user_in) - is_active = crud.user.is_active(user) - assert is_active is True + user = crud.create_user(session=db, user_create=user_in) + assert user.is_active is True def test_check_if_user_is_active_inactive(db: Session) -> None: email = random_email() password = random_lower_string() user_in = UserCreate(email=email, password=password, disabled=True) - user = crud.user.create(db, obj_in=user_in) - is_active = crud.user.is_active(user) - assert is_active + user = crud.create_user(session=db, user_create=user_in) + assert user.is_active def test_check_if_user_is_superuser(db: Session) -> None: email = random_email() password = random_lower_string() user_in = UserCreate(email=email, password=password, is_superuser=True) - user = crud.user.create(db, obj_in=user_in) - is_superuser = crud.user.is_superuser(user) - assert is_superuser is True + user = crud.create_user(session=db, user_create=user_in) + assert user.is_superuser is True def test_check_if_user_is_superuser_normal_user(db: Session) -> None: username = random_email() password = random_lower_string() user_in = UserCreate(email=username, password=password) - user = crud.user.create(db, obj_in=user_in) - is_superuser = crud.user.is_superuser(user) - assert is_superuser is False + user = crud.create_user(session=db, user_create=user_in) + assert user.is_superuser is False def test_get_user(db: Session) -> None: password = random_lower_string() username = random_email() user_in = UserCreate(email=username, password=password, is_superuser=True) - user = crud.user.create(db, obj_in=user_in) - user_2 = crud.user.get(db, id=user.id) + user = crud.create_user(session=db, user_create=user_in) + user_2 = db.get(User, user.id) assert user_2 assert user.email == user_2.email assert jsonable_encoder(user) == jsonable_encoder(user_2) @@ -84,11 +80,12 @@ def test_update_user(db: Session) -> None: password = random_lower_string() email = random_email() user_in = UserCreate(email=email, password=password, is_superuser=True) - user = crud.user.create(db, obj_in=user_in) + user = crud.create_user(session=db, user_create=user_in) new_password = random_lower_string() user_in_update = UserUpdate(password=new_password, is_superuser=True) - crud.user.update(db, db_obj=user, obj_in=user_in_update) - user_2 = crud.user.get(db, id=user.id) + if user.id is not None: + crud.update_user(session=db, user_id=user.id, user_in=user_in_update) + user_2 = db.get(User, user.id) assert user_2 assert user.email == user_2.email assert verify_password(new_password, user_2.hashed_password) diff --git a/src/backend/app/tests/utils/item.py b/src/backend/app/tests/utils/item.py index 1ba088a946..6e32b3a84a 100644 --- a/src/backend/app/tests/utils/item.py +++ b/src/backend/app/tests/utils/item.py @@ -1,16 +1,16 @@ -from sqlalchemy.orm import Session +from sqlmodel import Session -from app import crud, models -from app.schemas.item import ItemCreate +from app import crud +from app.models import Item, ItemCreate from app.tests.utils.user import create_random_user from app.tests.utils.utils import random_lower_string -def create_random_item(db: Session, *, owner_id: int | None = None) -> models.Item: - if owner_id is None: - user = create_random_user(db) - owner_id = user.id +def create_random_item(db: Session) -> Item: + user = create_random_user(db) + owner_id = user.id + assert owner_id is not None title = random_lower_string() description = random_lower_string() - item_in = ItemCreate(title=title, description=description, id=id) - return crud.item.create_with_owner(db=db, obj_in=item_in, owner_id=owner_id) + item_in = ItemCreate(title=title, description=description) + return crud.create_item(session=db, item_in=item_in, owner_id=owner_id) diff --git a/src/backend/app/tests/utils/user.py b/src/backend/app/tests/utils/user.py index e08c0b25fb..4622c2d3f5 100644 --- a/src/backend/app/tests/utils/user.py +++ b/src/backend/app/tests/utils/user.py @@ -1,10 +1,9 @@ from fastapi.testclient import TestClient -from sqlalchemy.orm import Session +from sqlmodel import Session from app import crud from app.core.config import settings -from app.models import User -from app.schemas.user import UserCreate, UserUpdate +from app.models import User, UserCreate, UserUpdate from app.tests.utils.utils import random_email, random_lower_string @@ -23,8 +22,8 @@ def user_authentication_headers( def create_random_user(db: Session) -> User: email = random_email() password = random_lower_string() - user_in = UserCreate(username=email, email=email, password=password) - user = crud.user.create(db=db, obj_in=user_in) + user_in = UserCreate(email=email, password=password) + user = crud.create_user(session=db, user_create=user_in) return user @@ -37,12 +36,12 @@ def authentication_token_from_email( If the user doesn't exist it is created first. """ password = random_lower_string() - user = crud.user.get_by_email(db, email=email) + user = crud.get_user_by_email(session=db, email=email) if not user: - user_in_create = UserCreate(username=email, email=email, password=password) - user = crud.user.create(db, obj_in=user_in_create) + user_in_create = UserCreate(email=email, password=password) + user = crud.create_user(session=db, user_create=user_in_create) else: user_in_update = UserUpdate(password=password) - user = crud.user.update(db, db_obj=user, obj_in=user_in_update) + user = crud.update_user(session=db, user_id=user.id, user_in=user_in_update) return user_authentication_headers(client=client, email=email, password=password) From d03d5afc02df99332802d450013a8257668cb4fd Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 20:43:13 +0000 Subject: [PATCH 217/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 23b9af3e20..2ab179c74a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -49,6 +49,7 @@ ### Refactors +* ♻️ Refactor old CRUD utils and tests. PR [#622](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/622) by [@alejsdev](https://github.com/alejsdev). * 🔧 Update .env to allow local debug for the backend. PR [#618](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/618) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor and update CORS, remove trailing slash from new Pydantic v2. PR [#617](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/617) by [@tiangolo](https://github.com/tiangolo). * 🎨 Format files with pre-commit and Ruff. PR [#611](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/611) by [@tiangolo](https://github.com/tiangolo). From 76e053bb927c528bfd586d30efede0c348bfdb73 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:30:58 -0500 Subject: [PATCH 218/771] =?UTF-8?q?=E2=9C=A8=20Add=20password=20reset=20fu?= =?UTF-8?q?nctionality=20(#624)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/app/utils.py | 8 +-- .../src/pages/RecoverPassword.tsx | 26 +++---- src/new-frontend/src/pages/ResetPassword.tsx | 72 +++++++++++++++++++ src/new-frontend/src/routes/public_route.tsx | 2 + 4 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 src/new-frontend/src/pages/ResetPassword.tsx diff --git a/src/backend/app/utils.py b/src/backend/app/utils.py index ec788efa7c..05b75b37c6 100644 --- a/src/backend/app/utils.py +++ b/src/backend/app/utils.py @@ -43,7 +43,7 @@ def send_test_email(email_to: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - current_environment={"project_name": settings.PROJECT_NAME, "email": email_to}, + environment={"project_name": settings.PROJECT_NAME, "email": email_to}, ) @@ -58,7 +58,7 @@ def send_reset_password_email(email_to: str, email: str, token: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - current_environment={ + environment={ "project_name": settings.PROJECT_NAME, "username": email, "email": email_to, @@ -78,7 +78,7 @@ def send_new_account_email(email_to: str, username: str, password: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - current_environment={ + environment={ "project_name": settings.PROJECT_NAME, "username": username, "password": password, @@ -104,6 +104,6 @@ def generate_password_reset_token(email: str) -> str: def verify_password_reset_token(token: str) -> str | None: try: decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) - return decoded_token["email"] + return decoded_token["sub"] except jwt.JWTError: return None diff --git a/src/new-frontend/src/pages/RecoverPassword.tsx b/src/new-frontend/src/pages/RecoverPassword.tsx index cbe8ff77e8..1ed681f24e 100644 --- a/src/new-frontend/src/pages/RecoverPassword.tsx +++ b/src/new-frontend/src/pages/RecoverPassword.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Button, Container, FormControl, Heading, Input, Text } from "@chakra-ui/react"; +import { Button, Container, FormControl, FormErrorMessage, Heading, Input, Text } from "@chakra-ui/react"; import { SubmitHandler, useForm } from "react-hook-form"; import { LoginService } from "../client"; @@ -11,14 +11,13 @@ interface FormData { } const RecoverPassword: React.FC = () => { - const { register, handleSubmit } = useForm(); + const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm(); const showToast = useCustomToast(); const onSubmit: SubmitHandler = async (data) => { - const response = await LoginService.recoverPassword({ + await LoginService.recoverPassword({ email: data.email, }); - console.log(response) showToast("Email sent.", "We sent an email with a link to get back into your account.", "success"); }; @@ -37,19 +36,14 @@ const RecoverPassword: React.FC = () => { Password Recovery - - - A password recovery email will be sent to the registered account. - - + + A password recovery email will be sent to the registered account. + + + + {errors.email && {errors.email.message}} - diff --git a/src/new-frontend/src/pages/ResetPassword.tsx b/src/new-frontend/src/pages/ResetPassword.tsx new file mode 100644 index 0000000000..e80f2e9676 --- /dev/null +++ b/src/new-frontend/src/pages/ResetPassword.tsx @@ -0,0 +1,72 @@ +import React from "react"; + +import { Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, Text } from "@chakra-ui/react"; +import { SubmitHandler, useForm } from "react-hook-form"; + +import { LoginService, NewPassword } from "../client"; +import useCustomToast from "../hooks/useCustomToast"; + +interface NewPasswordForm extends NewPassword { + confirm_password: string; +} + +const ResetPassword: React.FC = () => { + const { register, handleSubmit, getValues, formState: { errors } } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + new_password: '', + } + }); + const showToast = useCustomToast(); + + const onSubmit: SubmitHandler = async (data) => { + try { + const token = new URLSearchParams(window.location.search).get('token'); + await LoginService.resetPassword({ + requestBody: { new_password: data.new_password, token: token! } + }); + showToast("Password reset.", "Your password has been reset successfully.", "success"); + } catch (error) { + showToast("Error", "An error occurred while resetting your password.", "error"); + } + }; + + return ( + + + Reset Password + + + Please enter your new password and confirm it to reset your password. + + + Set Password + + {errors.new_password && {errors.new_password.message}} + + + Confirm Password + value === getValues().new_password || 'The passwords do not match' + })} placeholder='Password' type='password' /> + {errors.confirm_password && {errors.confirm_password.message}} + + + + ); +}; + +export default ResetPassword; \ No newline at end of file diff --git a/src/new-frontend/src/routes/public_route.tsx b/src/new-frontend/src/routes/public_route.tsx index 8b4dd4c748..cffa5a3311 100644 --- a/src/new-frontend/src/routes/public_route.tsx +++ b/src/new-frontend/src/routes/public_route.tsx @@ -1,11 +1,13 @@ import ErrorPage from '../pages/ErrorPage'; import Login from '../pages/Login'; import RecoverPassword from '../pages/RecoverPassword'; +import ResetPassword from '../pages/ResetPassword'; export default function publicRoutes() { return [ { path: '/login', element: , errorElement: }, { path: 'recover-password', element: , errorElement: }, + { path: 'reset-password', element: , errorElement: }, // TODO: complete this // { path: '*', element: } ]; From 846897622071b3b2b030390e7e8ef9f4b96bc1ea Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 22:31:16 +0000 Subject: [PATCH 219/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 2ab179c74a..f858338909 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Features +* ✨ Add password reset functionality. PR [#624](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/624) by [@alejsdev](https://github.com/alejsdev). * ✨ Add private/public routing. PR [#621](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/621) by [@alejsdev](https://github.com/alejsdev). * 🔧 Add VS Code debug configs. PR [#620](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/620) by [@tiangolo](https://github.com/tiangolo). * ✨ Add `Not Found` page. PR [#595](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/595) by [@alejsdev](https://github.com/alejsdev). From dfe55f4b547b3568b9de467837445d8f74e179c6 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:12:55 -0500 Subject: [PATCH 220/771] =?UTF-8?q?=E2=9C=A8=20Update=20new-frontend=20cli?= =?UTF-8?q?ent=20(#625)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new-frontend/src/client/index.ts | 4 ++ .../models/Body_login_login_access_token.ts | 6 +-- .../src/client/models/ItemCreate.ts | 2 +- src/new-frontend/src/client/models/ItemOut.ts | 3 +- .../src/client/models/ItemUpdate.ts | 4 +- .../src/client/models/ItemsOut.ts | 11 ++++++ .../src/client/models/UserCreate.ts | 2 +- .../src/client/models/UserCreateOpen.ts | 2 +- src/new-frontend/src/client/models/UserOut.ts | 2 +- .../src/client/models/UserUpdate.ts | 6 +-- .../src/client/models/UserUpdateMe.ts | 4 +- .../src/client/models/UsersOut.ts | 11 ++++++ .../schemas/$Body_login_login_access_token.ts | 15 ++++++++ .../src/client/schemas/$ItemCreate.ts | 5 +++ .../src/client/schemas/$ItemOut.ts | 9 +++++ .../src/client/schemas/$ItemUpdate.ts | 10 +++++ .../src/client/schemas/$ItemsOut.ts | 19 ++++++++++ .../src/client/schemas/$UserCreate.ts | 6 ++- .../src/client/schemas/$UserCreateOpen.ts | 6 ++- .../src/client/schemas/$UserOut.ts | 6 ++- .../src/client/schemas/$UserUpdate.ts | 16 +++++++- .../src/client/schemas/$UserUpdateMe.ts | 11 +++++- .../src/client/schemas/$UsersOut.ts | 19 ++++++++++ .../src/client/services/ItemsService.ts | 5 ++- .../src/client/services/UsersService.ts | 37 ++++++++++--------- 25 files changed, 181 insertions(+), 40 deletions(-) create mode 100644 src/new-frontend/src/client/models/ItemsOut.ts create mode 100644 src/new-frontend/src/client/models/UsersOut.ts create mode 100644 src/new-frontend/src/client/schemas/$ItemsOut.ts create mode 100644 src/new-frontend/src/client/schemas/$UsersOut.ts diff --git a/src/new-frontend/src/client/index.ts b/src/new-frontend/src/client/index.ts index adc379de3e..fb94ef6992 100644 --- a/src/new-frontend/src/client/index.ts +++ b/src/new-frontend/src/client/index.ts @@ -11,6 +11,7 @@ export type { Body_login_login_access_token } from './models/Body_login_login_ac export type { HTTPValidationError } from './models/HTTPValidationError'; export type { ItemCreate } from './models/ItemCreate'; export type { ItemOut } from './models/ItemOut'; +export type { ItemsOut } from './models/ItemsOut'; export type { ItemUpdate } from './models/ItemUpdate'; export type { Message } from './models/Message'; export type { NewPassword } from './models/NewPassword'; @@ -19,6 +20,7 @@ export type { UpdatePassword } from './models/UpdatePassword'; export type { UserCreate } from './models/UserCreate'; export type { UserCreateOpen } from './models/UserCreateOpen'; export type { UserOut } from './models/UserOut'; +export type { UsersOut } from './models/UsersOut'; export type { UserUpdate } from './models/UserUpdate'; export type { UserUpdateMe } from './models/UserUpdateMe'; export type { ValidationError } from './models/ValidationError'; @@ -27,6 +29,7 @@ export { $Body_login_login_access_token } from './schemas/$Body_login_login_acce export { $HTTPValidationError } from './schemas/$HTTPValidationError'; export { $ItemCreate } from './schemas/$ItemCreate'; export { $ItemOut } from './schemas/$ItemOut'; +export { $ItemsOut } from './schemas/$ItemsOut'; export { $ItemUpdate } from './schemas/$ItemUpdate'; export { $Message } from './schemas/$Message'; export { $NewPassword } from './schemas/$NewPassword'; @@ -35,6 +38,7 @@ export { $UpdatePassword } from './schemas/$UpdatePassword'; export { $UserCreate } from './schemas/$UserCreate'; export { $UserCreateOpen } from './schemas/$UserCreateOpen'; export { $UserOut } from './schemas/$UserOut'; +export { $UsersOut } from './schemas/$UsersOut'; export { $UserUpdate } from './schemas/$UserUpdate'; export { $UserUpdateMe } from './schemas/$UserUpdateMe'; export { $ValidationError } from './schemas/$ValidationError'; diff --git a/src/new-frontend/src/client/models/Body_login_login_access_token.ts b/src/new-frontend/src/client/models/Body_login_login_access_token.ts index bc8efd0ea0..7798e611b6 100644 --- a/src/new-frontend/src/client/models/Body_login_login_access_token.ts +++ b/src/new-frontend/src/client/models/Body_login_login_access_token.ts @@ -4,10 +4,10 @@ /* eslint-disable */ export type Body_login_login_access_token = { - grant_type?: string; + grant_type?: (string | null); username: string; password: string; scope?: string; - client_id?: string; - client_secret?: string; + client_id?: (string | null); + client_secret?: (string | null); }; diff --git a/src/new-frontend/src/client/models/ItemCreate.ts b/src/new-frontend/src/client/models/ItemCreate.ts index fbe7c16596..aeefb6ddca 100644 --- a/src/new-frontend/src/client/models/ItemCreate.ts +++ b/src/new-frontend/src/client/models/ItemCreate.ts @@ -5,5 +5,5 @@ export type ItemCreate = { title: string; - description?: string; + description?: (string | null); }; diff --git a/src/new-frontend/src/client/models/ItemOut.ts b/src/new-frontend/src/client/models/ItemOut.ts index 06787f5e17..cbe6c02c06 100644 --- a/src/new-frontend/src/client/models/ItemOut.ts +++ b/src/new-frontend/src/client/models/ItemOut.ts @@ -5,6 +5,7 @@ export type ItemOut = { title: string; - description?: string; + description?: (string | null); id: number; + owner_id: number; }; diff --git a/src/new-frontend/src/client/models/ItemUpdate.ts b/src/new-frontend/src/client/models/ItemUpdate.ts index ef82c3c44e..89ccb4e016 100644 --- a/src/new-frontend/src/client/models/ItemUpdate.ts +++ b/src/new-frontend/src/client/models/ItemUpdate.ts @@ -4,6 +4,6 @@ /* eslint-disable */ export type ItemUpdate = { - title?: string; - description?: string; + title?: (string | null); + description?: (string | null); }; diff --git a/src/new-frontend/src/client/models/ItemsOut.ts b/src/new-frontend/src/client/models/ItemsOut.ts new file mode 100644 index 0000000000..e45794bf0d --- /dev/null +++ b/src/new-frontend/src/client/models/ItemsOut.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ItemOut } from './ItemOut'; + +export type ItemsOut = { + data: Array; + count: number; +}; diff --git a/src/new-frontend/src/client/models/UserCreate.ts b/src/new-frontend/src/client/models/UserCreate.ts index 9970feb96f..f49d0bff7d 100644 --- a/src/new-frontend/src/client/models/UserCreate.ts +++ b/src/new-frontend/src/client/models/UserCreate.ts @@ -7,6 +7,6 @@ export type UserCreate = { email: string; is_active?: boolean; is_superuser?: boolean; - full_name?: string; + full_name?: (string | null); password: string; }; diff --git a/src/new-frontend/src/client/models/UserCreateOpen.ts b/src/new-frontend/src/client/models/UserCreateOpen.ts index 27ca0f1d2f..f859e9b1fa 100644 --- a/src/new-frontend/src/client/models/UserCreateOpen.ts +++ b/src/new-frontend/src/client/models/UserCreateOpen.ts @@ -6,5 +6,5 @@ export type UserCreateOpen = { email: string; password: string; - full_name?: string; + full_name?: (string | null); }; diff --git a/src/new-frontend/src/client/models/UserOut.ts b/src/new-frontend/src/client/models/UserOut.ts index 9f2faf59a6..8387dc6db9 100644 --- a/src/new-frontend/src/client/models/UserOut.ts +++ b/src/new-frontend/src/client/models/UserOut.ts @@ -7,6 +7,6 @@ export type UserOut = { email: string; is_active?: boolean; is_superuser?: boolean; - full_name?: string; + full_name?: (string | null); id: number; }; diff --git a/src/new-frontend/src/client/models/UserUpdate.ts b/src/new-frontend/src/client/models/UserUpdate.ts index d64e3da644..9ba346a41b 100644 --- a/src/new-frontend/src/client/models/UserUpdate.ts +++ b/src/new-frontend/src/client/models/UserUpdate.ts @@ -4,9 +4,9 @@ /* eslint-disable */ export type UserUpdate = { - email?: string; + email?: (string | null); is_active?: boolean; is_superuser?: boolean; - full_name?: string; - password?: string; + full_name?: (string | null); + password?: (string | null); }; diff --git a/src/new-frontend/src/client/models/UserUpdateMe.ts b/src/new-frontend/src/client/models/UserUpdateMe.ts index 84ee306e6a..aa7a2fbbc7 100644 --- a/src/new-frontend/src/client/models/UserUpdateMe.ts +++ b/src/new-frontend/src/client/models/UserUpdateMe.ts @@ -4,6 +4,6 @@ /* eslint-disable */ export type UserUpdateMe = { - full_name?: string; - email?: string; + full_name?: (string | null); + email?: (string | null); }; diff --git a/src/new-frontend/src/client/models/UsersOut.ts b/src/new-frontend/src/client/models/UsersOut.ts new file mode 100644 index 0000000000..0e317ab469 --- /dev/null +++ b/src/new-frontend/src/client/models/UsersOut.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { UserOut } from './UserOut'; + +export type UsersOut = { + data: Array; + count: number; +}; diff --git a/src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts b/src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts index 81547d339b..5d701cc41f 100644 --- a/src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts +++ b/src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts @@ -5,8 +5,13 @@ export const $Body_login_login_access_token = { properties: { grant_type: { + type: 'any-of', + contains: [{ type: 'string', pattern: 'password', +}, { + type: 'null', +}], }, username: { type: 'string', @@ -20,10 +25,20 @@ export const $Body_login_login_access_token = { type: 'string', }, client_id: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, client_secret: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, }, } as const; diff --git a/src/new-frontend/src/client/schemas/$ItemCreate.ts b/src/new-frontend/src/client/schemas/$ItemCreate.ts index 11851241f8..70037eebfc 100644 --- a/src/new-frontend/src/client/schemas/$ItemCreate.ts +++ b/src/new-frontend/src/client/schemas/$ItemCreate.ts @@ -9,7 +9,12 @@ export const $ItemCreate = { isRequired: true, }, description: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, }, } as const; diff --git a/src/new-frontend/src/client/schemas/$ItemOut.ts b/src/new-frontend/src/client/schemas/$ItemOut.ts index 6eac037db4..015a518191 100644 --- a/src/new-frontend/src/client/schemas/$ItemOut.ts +++ b/src/new-frontend/src/client/schemas/$ItemOut.ts @@ -9,11 +9,20 @@ export const $ItemOut = { isRequired: true, }, description: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, id: { type: 'number', isRequired: true, +}, + owner_id: { + type: 'number', + isRequired: true, }, }, } as const; diff --git a/src/new-frontend/src/client/schemas/$ItemUpdate.ts b/src/new-frontend/src/client/schemas/$ItemUpdate.ts index 25250d7e13..bf76d61a73 100644 --- a/src/new-frontend/src/client/schemas/$ItemUpdate.ts +++ b/src/new-frontend/src/client/schemas/$ItemUpdate.ts @@ -5,10 +5,20 @@ export const $ItemUpdate = { properties: { title: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, description: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, }, } as const; diff --git a/src/new-frontend/src/client/schemas/$ItemsOut.ts b/src/new-frontend/src/client/schemas/$ItemsOut.ts new file mode 100644 index 0000000000..782b68d314 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$ItemsOut.ts @@ -0,0 +1,19 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ItemsOut = { + properties: { + data: { + type: 'array', + contains: { + type: 'ItemOut', + }, + isRequired: true, +}, + count: { + type: 'number', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$UserCreate.ts b/src/new-frontend/src/client/schemas/$UserCreate.ts index 2275432a9e..84503be3fe 100644 --- a/src/new-frontend/src/client/schemas/$UserCreate.ts +++ b/src/new-frontend/src/client/schemas/$UserCreate.ts @@ -7,7 +7,6 @@ export const $UserCreate = { email: { type: 'string', isRequired: true, - format: 'email', }, is_active: { type: 'boolean', @@ -16,7 +15,12 @@ export const $UserCreate = { type: 'boolean', }, full_name: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, password: { type: 'string', diff --git a/src/new-frontend/src/client/schemas/$UserCreateOpen.ts b/src/new-frontend/src/client/schemas/$UserCreateOpen.ts index 7aa75cd808..ae2fff5b52 100644 --- a/src/new-frontend/src/client/schemas/$UserCreateOpen.ts +++ b/src/new-frontend/src/client/schemas/$UserCreateOpen.ts @@ -7,14 +7,18 @@ export const $UserCreateOpen = { email: { type: 'string', isRequired: true, - format: 'email', }, password: { type: 'string', isRequired: true, }, full_name: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, }, } as const; diff --git a/src/new-frontend/src/client/schemas/$UserOut.ts b/src/new-frontend/src/client/schemas/$UserOut.ts index c920f098d1..82614024af 100644 --- a/src/new-frontend/src/client/schemas/$UserOut.ts +++ b/src/new-frontend/src/client/schemas/$UserOut.ts @@ -7,7 +7,6 @@ export const $UserOut = { email: { type: 'string', isRequired: true, - format: 'email', }, is_active: { type: 'boolean', @@ -16,7 +15,12 @@ export const $UserOut = { type: 'boolean', }, full_name: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, id: { type: 'number', diff --git a/src/new-frontend/src/client/schemas/$UserUpdate.ts b/src/new-frontend/src/client/schemas/$UserUpdate.ts index f0f38ac3c1..396e617340 100644 --- a/src/new-frontend/src/client/schemas/$UserUpdate.ts +++ b/src/new-frontend/src/client/schemas/$UserUpdate.ts @@ -5,8 +5,12 @@ export const $UserUpdate = { properties: { email: { + type: 'any-of', + contains: [{ type: 'string', - format: 'email', +}, { + type: 'null', +}], }, is_active: { type: 'boolean', @@ -15,10 +19,20 @@ export const $UserUpdate = { type: 'boolean', }, full_name: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, password: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, }, } as const; diff --git a/src/new-frontend/src/client/schemas/$UserUpdateMe.ts b/src/new-frontend/src/client/schemas/$UserUpdateMe.ts index 0aaf6abbef..ed9c011233 100644 --- a/src/new-frontend/src/client/schemas/$UserUpdateMe.ts +++ b/src/new-frontend/src/client/schemas/$UserUpdateMe.ts @@ -5,11 +5,20 @@ export const $UserUpdateMe = { properties: { full_name: { + type: 'any-of', + contains: [{ type: 'string', +}, { + type: 'null', +}], }, email: { + type: 'any-of', + contains: [{ type: 'string', - format: 'email', +}, { + type: 'null', +}], }, }, } as const; diff --git a/src/new-frontend/src/client/schemas/$UsersOut.ts b/src/new-frontend/src/client/schemas/$UsersOut.ts new file mode 100644 index 0000000000..9c228688c8 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$UsersOut.ts @@ -0,0 +1,19 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $UsersOut = { + properties: { + data: { + type: 'array', + contains: { + type: 'UserOut', + }, + isRequired: true, +}, + count: { + type: 'number', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/services/ItemsService.ts b/src/new-frontend/src/client/services/ItemsService.ts index 0f46422dd3..b68a28ac46 100644 --- a/src/new-frontend/src/client/services/ItemsService.ts +++ b/src/new-frontend/src/client/services/ItemsService.ts @@ -4,6 +4,7 @@ /* eslint-disable */ import type { ItemCreate } from '../models/ItemCreate'; import type { ItemOut } from '../models/ItemOut'; +import type { ItemsOut } from '../models/ItemsOut'; import type { ItemUpdate } from '../models/ItemUpdate'; import type { Message } from '../models/Message'; @@ -16,7 +17,7 @@ export class ItemsService { /** * Read Items * Retrieve items. - * @returns ItemOut Successful Response + * @returns ItemsOut Successful Response * @throws ApiError */ public static readItems({ @@ -25,7 +26,7 @@ limit = 100, }: { skip?: number, limit?: number, -}): CancelablePromise> { +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/items/', diff --git a/src/new-frontend/src/client/services/UsersService.ts b/src/new-frontend/src/client/services/UsersService.ts index 16481fc8b4..055debe53b 100644 --- a/src/new-frontend/src/client/services/UsersService.ts +++ b/src/new-frontend/src/client/services/UsersService.ts @@ -7,6 +7,7 @@ import type { UpdatePassword } from '../models/UpdatePassword'; import type { UserCreate } from '../models/UserCreate'; import type { UserCreateOpen } from '../models/UserCreateOpen'; import type { UserOut } from '../models/UserOut'; +import type { UsersOut } from '../models/UsersOut'; import type { UserUpdate } from '../models/UserUpdate'; import type { UserUpdateMe } from '../models/UserUpdateMe'; @@ -19,7 +20,7 @@ export class UsersService { /** * Read Users * Retrieve users. - * @returns UserOut Successful Response + * @returns UsersOut Successful Response * @throws ApiError */ public static readUsers({ @@ -28,7 +29,7 @@ limit = 100, }: { skip?: number, limit?: number, -}): CancelablePromise> { +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/users/', @@ -167,22 +168,26 @@ userId: number, } /** - * Delete User - * Delete a user. - * @returns Message Successful Response + * Update User + * Update a user. + * @returns UserOut Successful Response * @throws ApiError */ - public static deleteUser({ + public static updateUser({ userId, +requestBody, }: { userId: number, -}): CancelablePromise { +requestBody: UserUpdate, +}): CancelablePromise { return __request(OpenAPI, { - method: 'DELETE', + method: 'PATCH', url: '/api/v1/users/{user_id}', path: { 'user_id': userId, }, + body: requestBody, + mediaType: 'application/json', errors: { 422: `Validation Error`, }, @@ -190,26 +195,22 @@ userId: number, } /** - * Update User - * Update a user. - * @returns UserOut Successful Response + * Delete User + * Delete a user. + * @returns Message Successful Response * @throws ApiError */ - public static updateUser({ + public static deleteUser({ userId, -requestBody, }: { userId: number, -requestBody: UserUpdate, -}): CancelablePromise { +}): CancelablePromise { return __request(OpenAPI, { - method: 'PATCH', + method: 'DELETE', url: '/api/v1/users/{user_id}', path: { 'user_id': userId, }, - body: requestBody, - mediaType: 'application/json', errors: { 422: `Validation Error`, }, From 05c0ccda6d5dfeaff6286d552ef30c18f945b0ca Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 1 Mar 2024 17:13:27 +0000 Subject: [PATCH 221/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index f858338909..9d7ea7ddc3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Features +* ✨ Update new-frontend client. PR [#625](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/625) by [@alejsdev](https://github.com/alejsdev). * ✨ Add password reset functionality. PR [#624](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/624) by [@alejsdev](https://github.com/alejsdev). * ✨ Add private/public routing. PR [#621](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/621) by [@alejsdev](https://github.com/alejsdev). * 🔧 Add VS Code debug configs. PR [#620](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/620) by [@tiangolo](https://github.com/tiangolo). From e752ee4019be71b8babcf5f3580b31b434fdac91 Mon Sep 17 00:00:00 2001 From: Esteban Maya Date: Fri, 1 Mar 2024 12:26:05 -0500 Subject: [PATCH 222/771] =?UTF-8?q?=E2=9C=85=20Add=20setup=20and=20teardow?= =?UTF-8?q?n=20database=20for=20tests=20(#626)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- src/backend/app/tests/conftest.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/backend/app/tests/conftest.py b/src/backend/app/tests/conftest.py index ffd097dc8c..74a0fb0cca 100644 --- a/src/backend/app/tests/conftest.py +++ b/src/backend/app/tests/conftest.py @@ -2,19 +2,27 @@ import pytest from fastapi.testclient import TestClient -from sqlmodel import Session +from sqlmodel import Session, delete from app.core.config import settings from app.db.engine import engine +from app.db.init_db import init_db from app.main import app +from app.models import Item, User from app.tests.utils.user import authentication_token_from_email from app.tests.utils.utils import get_superuser_token_headers -@pytest.fixture(scope="session") +@pytest.fixture(scope="session", autouse=True) def db() -> Generator: with Session(engine) as session: + init_db(session) yield session + statement = delete(Item) + session.execute(statement) + statement = delete(User) + session.execute(statement) + session.commit() @pytest.fixture(scope="module") From 975892d7ac8ee66715e6e9c73af8118ce177b68f Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 1 Mar 2024 17:26:25 +0000 Subject: [PATCH 223/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 9d7ea7ddc3..8d279a3d0c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Features +* ✅ Add setup and teardown database for tests. PR [#626](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/626) by [@estebanx64](https://github.com/estebanx64). * ✨ Update new-frontend client. PR [#625](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/625) by [@alejsdev](https://github.com/alejsdev). * ✨ Add password reset functionality. PR [#624](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/624) by [@alejsdev](https://github.com/alejsdev). * ✨ Add private/public routing. PR [#621](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/621) by [@alejsdev](https://github.com/alejsdev). From e00d9d5f74433e23858355c197deab41912dd5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 1 Mar 2024 21:18:16 +0100 Subject: [PATCH 224/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20README=20(#628)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 623 +++++++++++++++++++++++++++++++++++++++++--------- img/redoc.png | Bin 103288 -> 0 bytes src/README.md | 410 --------------------------------- 3 files changed, 515 insertions(+), 518 deletions(-) delete mode 100644 img/redoc.png delete mode 100644 src/README.md diff --git a/README.md b/README.md index 71a6aa360a..b15130416f 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,30 @@ -# Full Stack FastAPI and PostgreSQL - Base Project Generator +# FastAPI Project Template ## 🚨 Warning: in (re) construction 😎 🏗️ This project is currently being restructured, don't use it right now, hold for a bit. -In the next couple of months it will be ready. 😎 🚀 +In the next couple of weeks it will be ready. 😎 🚀 Some of the future new features and changes: -* Upgrade to the latest FastAPI. -* Migration from SQLAlchemy to SQLModel. -* Upgrade to Pydantic v2. -* Refactor and simplification of most of the code, a lot of the complexity won't be necessary anymore. -* Migrate from Vue.js 2 to React with hooks and TypeScript. -* Move from Docker Swarm Model to Kubernetes. -* GitHub Actions for CI. +- [x] Upgrade to the latest FastAPI. +- [x] Migration from SQLAlchemy to SQLModel. +- [x] Upgrade to Pydantic v2. +- [ ] Refactor and simplification of most of the code, a lot of the complexity won't be necessary anymore. +- [x] Automatic TypeScript frontend client generated from the FastAPI API (OpenAPI). +- [ ] Migrate from Vue.js 2 to React with hooks and TypeScript. +- [x] Make the project work as is, allowing to clone and use (not requiring to generate a project with Cookiecutter or Copier) +- [x] Migrate from Cookiecutter to Copier +- [ ] Move from Docker Swarm Model to Docker Compose for a simple deployment. +- [x] GitHub Actions for CI. --- -Generate a backend and frontend stack using Python, including interactive API documentation. - ### Interactive API documentation [![API docs](img/docs.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) -### Alternative API documentation - -[![API docs](img/redoc.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) - ### Dashboard Login [![API docs](img/login.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) @@ -36,122 +33,117 @@ Generate a backend and frontend stack using Python, including interactive API do [![API docs](img/dashboard.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) -## Features - -* Full **Docker** integration (Docker based). -* Docker Swarm Mode deployment. -* **Docker Compose** integration and optimization for local development. -* **Production ready** Python web server using Uvicorn and Gunicorn. -* Python **FastAPI** backend: - * **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). - * **Intuitive**: Great editor support. Completion everywhere. Less time debugging. - * **Easy**: Designed to be easy to use and learn. Less time reading docs. - * **Short**: Minimize code duplication. Multiple features from each parameter declaration. - * **Robust**: Get production-ready code. With automatic interactive documentation. - * **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI and JSON Schema. - * **Many other features** including automatic validation, serialization, interactive documentation, authentication with OAuth2 JWT tokens, etc. -* **Secure password** hashing by default. -* **JWT token** authentication. -* **SQLAlchemy** models (independent of Flask extensions, so they can be used with Celery workers directly). -* Basic starting models for users (modify and remove as you need). -* **Alembic** migrations. -* **CORS** (Cross Origin Resource Sharing). -* **Celery** worker that can import and use models and code from the rest of the backend selectively. -* REST backend tests based on **Pytest**, integrated with Docker, so you can test the full API interaction, independent on the database. As it runs in Docker, it can build a new data store from scratch each time (so you can use ElasticSearch, MongoDB, CouchDB, or whatever you want, and just test that the API works). -* Easy Python integration with **Jupyter Kernels** for remote or in-Docker development with extensions like Atom Hydrogen or Visual Studio Code Jupyter. -* **Vue** frontend: - * Generated with Vue CLI. - * **JWT Authentication** handling. - * Login view. - * After login, main dashboard view. - * Main dashboard with user creation and edition. - * Self user edition. - * **Vuex**. - * **Vue-router**. - * **Vuetify** for beautiful material design components. - * **TypeScript**. - * Docker server based on **Nginx** (configured to play nicely with Vue-router). - * Docker multi-stage building, so you don't need to save or commit compiled code. - * Frontend tests ran at build time (can be disabled too). - * Made as modular as possible, so it works out of the box, but you can re-generate with Vue CLI or create it as you need, and re-use what you want. - * It's also easy to remove it if you have an API-only app, check the instructions in the generated `README.md`. -* **PGAdmin** for PostgreSQL database, you can modify it to use PHPMyAdmin and MySQL easily. -* **Flower** for Celery jobs monitoring. -* Load balancing between frontend and backend with **Traefik**, so you can have both under the same domain, separated by path, but served by different containers. -* Traefik integration, including Let's Encrypt **HTTPS** certificates automatic generation. -* GitLab **CI** (continuous integration), including frontend and backend testing. +## Technology Stack and Features + +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) for the Python backend API. + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) for the Python SQL database interactions (ORM). + - 🔍 [Pydantic](https://docs.pydantic.dev), used by FastAPI, for the data validation and settings management. + - 💾 [PostgreSQL](https://www.postgresql.org) as the SQL database. +- 🚀 [React](https://react.dev) for the frontend. + - 💃 Using TypeScript, hooks, Vite, and other parts of a modern frontend stack. + - 🎨 [Chakra UI](https://chakra-ui.com) for the frontend components. + - 🤖 An automatically generated frontend client. +- 🐋 [Docker Compose](https://www.docker.com) for development and production. +- 🔒 Secure password hashing by default. +- 🔑 JWT token authentication. +- 📫 Email based password recovery. +- ✅ Tests with [Pytest](https://pytest.org). +- 📞 [Traefik](https://traefik.io) as a reverse proxy / load balancer. +- 🚢 Deployment instructions using Docker Compose, including how to set up a frontend Traefik proxy to handle automatic HTTPS certificates. +- 🏭 CI (continuous integration) and CD (continuous deployment) based on GitHub Actions. ## How to use it -Go to the directory where you want to create your project and run: +You can **just fork or clone** this repository and use it as is. + +✨ It just works. ✨ + +### Configure + +You can then update configs in the `.env` files to customize your configurations. + +Make sure you at least change the value for `SECRET_KEY` in the main `.env` file before deploying to production. + +### Generate secret keys + +You will be asked to provide passwords and **secret keys** for several components. + +They have a default value of `changethis`. You can also update them later in the `.env` files after generating the project. + +You could generate those secrets with: ```bash -pip install cookiecutter -cookiecutter https://github.com/tiangolo/full-stack-fastapi-postgresql +python -c "import secrets; print(secrets.token_urlsafe(32))" ``` -### Generate passwords +Copy the contents and use that as password / secret key. And run that again to generate another secure key. + +## How to use it - alternative with Copier + +This project template also supports generating a new project using [Copier](https://copier.readthedocs.io). + +It will copy all the files, ask you configuration questions, and update the `.env` files with your answers. + +### Install Copier -You will be asked to provide passwords and secret keys for several components. Open another terminal and run: +You can install Copier with: ```bash -openssl rand -hex 32 -# Outputs something like: 99d3b1f01aa639e4a76f4fc281fc834747a543720ba4c8a8648ba755aef9be7f +pip install copier ``` -Copy the contents and use that as password / secret key. And run that again to generate another secure key. +Or better, if you have [`pipx`](https://pipx.pypa.io/), you can run it with: +```bash +pipx install copier +``` -### Input variables +**Note**: If you have `pipx`, installing copier is optional, you could run it directly. -The generator (cookiecutter) will ask you for some data, you might want to have at hand before generating the project. +### Generate a Project with Copier -The input variables, with their default values (some auto generated) are: +Decide a name for your new project's directory, you will use it below. For example, `my-awesome-project`. -* `project_name`: The name of the project -* `project_slug`: The development friendly name of the project. By default, based on the project name -* `domain_main`: The domain in where to deploy the project for production (from the branch `production`), used by the load balancer, backend, etc. By default, based on the project slug. -* `domain_staging`: The domain in where to deploy while staging (before production) (from the branch `master`). By default, based on the main domain. -* `secret_key`: Backend server secret key. Use the method above to generate it. -* `first_superuser`: The first superuser generated, with it you will be able to create more users, etc. By default, based on the domain. -* `first_superuser_password`: First superuser password. Use the method above to generate it. -* `backend_cors_origins`: Origins (domains, more or less) that are enabled for CORS (Cross Origin Resource Sharing). This allows a frontend in one domain (e.g. `https://dashboard.example.com`) to communicate with this backend, that could be living in another domain (e.g. `https://api.example.com`). It can also be used to allow your local frontend (with a custom `hosts` domain mapping, as described in the project's `README.md`) that could be living in `http://dev.example.com:8080` to communicate with the backend at `https://stag.example.com`. Notice the `http` vs `https` and the `dev.` prefix for local development vs the "staging" `stag.` prefix. By default, it includes origins for production, staging and development, with ports commonly used during local development by several popular frontend frameworks (Vue with `:8080`, React, Angular). -* `smtp_port`: Port to use to send emails via SMTP. By default `587`. -* `smtp_host`: Host to use to send emails, it would be given by your email provider, like Mailgun, Sparkpost, etc. -* `smtp_user`: The user to use in the SMTP connection. The value will be given by your email provider. -* `smtp_password`: The password to be used in the SMTP connection. The value will be given by the email provider. -* `smtp_emails_from_email`: The email account to use as the sender in the notification emails, it would be something like `info@your-custom-domain.com`. - -* `postgres_password`: Postgres database password. Use the method above to generate it. (You could easily modify it to use MySQL, MariaDB, etc). -* `pgadmin_default_user`: PGAdmin default user, to log-in to the PGAdmin interface. -* `pgadmin_default_user_password`: PGAdmin default user password. Generate it with the method above. - -* `traefik_constraint_tag`: The tag to be used by the internal Traefik load balancer (for example, to divide requests between backend and frontend) for production. Used to separate this stack from any other stack you might have. This should identify each stack in each environment (production, staging, etc). -* `traefik_constraint_tag_staging`: The Traefik tag to be used while on staging. -* `traefik_public_constraint_tag`: The tag that should be used by stack services that should communicate with the public. - -* `flower_auth`: Basic HTTP authentication for flower, in the form`user:password`. By default: "`admin:changethis`". - -* `sentry_dsn`: Key URL (DSN) of Sentry, for live error reporting. You can use the open source version or a free account. E.g.: `https://1234abcd:5678ef@sentry.example.com/30`. - -* `docker_image_prefix`: Prefix to use for Docker image names. If you are using GitLab Docker registry it would be based on your code repository. E.g.: `git.example.com/development-team/my-awesome-project/`. -* `docker_image_backend`: Docker image name for the backend. By default, it will be based on your Docker image prefix, e.g.: `git.example.com/development-team/my-awesome-project/backend`. And depending on your environment, a different tag will be appended ( `prod`, `stag`, `branch` ). So, the final image names used will be like: `git.example.com/development-team/my-awesome-project/backend:prod`. -* `docker_image_celeryworker`: Docker image for the celery worker. By default, based on your Docker image prefix. -* `docker_image_frontend`: Docker image for the frontend. By default, based on your Docker image prefix. +Go to the directory that will be the parent of your project, and run the command with your project's name: -## How to deploy +```bash +copier copy https://github.com/tiangolo/full-stack-fastapi-postgresql my-awesome-project --trust +``` -This stack can be adjusted and used with several deployment options that are compatible with Docker Compose, but it is designed to be used in a cluster controlled with pure Docker in Swarm Mode with a Traefik main load balancer proxy handling automatic HTTPS certificates, using the ideas from DockerSwarm.rocks. +If you have `pipx` and you didn't install `copier`, you can run it directly: -Please refer to DockerSwarm.rocks to see how to deploy such a cluster in 20 minutes. +```bash +pipx run copier copy https://github.com/tiangolo/full-stack-fastapi-postgresql my-awesome-project --trust +``` + +**Note** the `--trust` option is necessary to be able to execute a [post-creation script](https://github.com/tiangolo/full-stack-fastapi-postgresql/blob/master/.copier/update_dotenv.py) that updates your `.env` files. + +### Input variables + +Copier will ask you for some data, you might want to have at hand before generating the project. -## More details +But don't worry, you can just update any of that in the `.env` files afterwards. -After using this generator, your new project (the directory created) will contain an extensive `README.md` with instructions for development, deployment, etc. You can pre-read [the project `README.md` template here too](./{{cookiecutter.project_slug}}/README.md). +The input variables, with their default values (some auto generated) are: -## Sibling project generators +- `domain`: (default: `"localhost"`) Which domain name to use for the project, by default, localhost, but you should change it later (in .env). +- `project_name`: (default: `"FastAPI Project"`) The name of the project, shown to API users (in .env). +- `stack_name`: (default: `"fastapi-project"`) The name of the stack used for Docker Compose labels (no spaces) (in .env). +- `secret_key`: (default: `"changethis"`) The secret key for the project, used for security, stored in .env, you can generate one with the method above. +- `first_superuser`: (default: `"admin@example.com"`) The email of the first superuser (in .env). +- `first_superuser_password`: (default: `"changethis"`) The password of the first superuser (in .env). +- `smtp_host`: (default: "") The SMTP server host to send emails, you can set it later in .env. +- `smtp_user`: (default: "") The SMTP server user to send emails, you can set it later in .env. +- `smtp_password`: (default: "") The SMTP server password to send emails, you can set it later in .env. +- `emails_from_email`: (default: `"info@example.com"`) The email account to send emails from, you can set it later in .env. +- `postgres_password`: (default: `"changethis"`) The password for the PostgreSQL database, stored in .env, you can generate one with the method above. +- `pgadmin_default_user`: (default: `"admin"`) The default user for pgAdmin, you can set it later in .env. +- `pgadmin_default_password`: (default: `"changethis"`) The default user password for pgAdmin, stored in .env. +- `sentry_dsn`: (default: "") The DSN for Sentry, if you are using it, you can set it later in .env. -* Full Stack FastAPI Couchbase: [https://github.com/tiangolo/full-stack-fastapi-couchbase](https://github.com/tiangolo/full-stack-fastapi-couchbase). +## How to deploy + +Deploy using Docker Compose and Traefik as a reverse proxy / load balancer handling automatic HTTPS certificates. ## Release Notes @@ -159,4 +151,419 @@ Check the file [release-notes.md](./release-notes.md). ## License -This project is licensed under the terms of the MIT license. +The FastAPI Project Template is licensed under the terms of the MIT license. + +--- + +The documentation below is for **your own project**, not the Project Template. 👇 + +# FastAPI Project + +## Backend Requirements + +* [Docker](https://www.docker.com/). +* [Poetry](https://python-poetry.org/) for Python package and environment management. + +## Frontend Requirements + +* Node.js (with `npm`). + +## Backend local development + +* Start the stack with Docker Compose: + +```bash +docker compose up -d +``` + +* Now you can open your browser and interact with these URLs: + +Frontend, built with Docker, with routes handled based on the path: http://localhost + +Backend, JSON based web API based on OpenAPI: http://localhost/api/ + +Automatic interactive documentation with Swagger UI (from the OpenAPI backend): http://localhost/docs + +PGAdmin, PostgreSQL web administration: http://localhost:5050 + +Flower, administration of Celery tasks: http://localhost:5555 + +Traefik UI, to see how the routes are being handled by the proxy: http://localhost:8090 + +**Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it. + +To check the logs, run: + +```bash +docker compose logs +``` + +To check the logs of a specific service, add the name of the service, e.g.: + +```bash +docker compose logs backend +``` + +If your Docker is not running in `localhost` (the URLs above wouldn't work) you would need to use the IP or domain where your Docker is running. + +## Backend local development, additional details + +### General workflow + +By default, the dependencies are managed with [Poetry](https://python-poetry.org/), go there and install it. + +From `./backend/` you can install all the dependencies with: + +```console +$ poetry install +``` + +Then you can start a shell session with the new environment with: + +```console +$ poetry shell +``` + +Next, open your editor at `./backend/` (instead of the project root: `./`), so that you see an `./app/` directory with your code inside. That way, your editor will be able to find all the imports, etc. Make sure your editor uses the environment you just created with Poetry. + +Modify or add SQLModel models for data and SQL tables in `./backend/app/models.py`, API endpoints in `./backend/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/crud.py`. + +Add and modify tasks to the Celery worker in `./backend/app/worker.py`. + +### Docker Compose Override + +During development, you can change Docker Compose settings that will only affect the local development environment in the file `docker-compose.override.yml`. + +The changes to that file only affect the local development environment, not the production environment. So, you can add "temporary" changes that help the development workflow. + +For example, the directory with the backend code is mounted as a Docker "host volume", mapping the code you change live to the directory inside the container. That allows you to test your changes right away, without having to build the Docker image again. It should only be done during development, for production, you should build the Docker image with a recent version of the backend code. But during development, it allows you to iterate very fast. + +There is also a command override that runs `/start-reload.sh` (included in the base image) instead of the default `/start.sh` (also included in the base image). It starts a single server process (instead of multiple, as would be for production) and reloads the process whenever the code changes. Have in mind that if you have a syntax error and save the Python file, it will break and exit, and the container will stop. After that, you can restart the container by fixing the error and running again: + +```console +$ docker compose up -d +``` + +There is also a commented out `command` override, you can uncomment it and comment the default one. It makes the backend container run a process that does "nothing", but keeps the container alive. That allows you to get inside your running container and execute commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes. + +To get inside the container with a `bash` session you can start the stack with: + +```console +$ docker compose up -d +``` + +and then `exec` inside the running container: + +```console +$ docker compose exec backend bash +``` + +You should see an output like: + +```console +root@7f2607af31c3:/app# +``` + +that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory, this directory has another directory called "app" inside, that's where your code lives inside the container: `/app/app`. + +There you can use the script `/start-reload.sh` to run the debug live reloading server. You can run that script from inside the container with: + +```console +$ bash /start-reload.sh +``` + +...it will look like: + +```console +root@7f2607af31c3:/app# bash /start-reload.sh +``` + +and then hit enter. That runs the live reloading server that auto reloads when it detects code changes. + +Nevertheless, if it doesn't detect a change but a syntax error, it will just stop with an error. But as the container is still alive and you are in a Bash session, you can quickly restart it after fixing the error, running the same command ("up arrow" and "Enter"). + +...this previous detail is what makes it useful to have the container alive doing nothing and then, in a Bash session, make it run the live reload server. + +### Backend tests + +To test the backend run: + +```console +$ bash ./scripts/test.sh +``` + +The tests run with Pytest, modify and add tests to `./backend/app/tests/`. + +If you use GitHub Actions the tests will run automatically. + +#### Test running stack + +If your stack is already up and you just want to run the tests, you can use: + +```bash +docker compose exec backend /app/tests-start.sh +``` + +That `/app/tests-start.sh` script just calls `pytest` after making sure that the rest of the stack is running. If you need to pass extra arguments to `pytest`, you can pass them to that command and they will be forwarded. + +For example, to stop on first error: + +```bash +docker compose exec backend bash /app/tests-start.sh -x +``` + +#### Test Coverage + +Because the test scripts forward arguments to `pytest`, you can enable test coverage HTML report generation by passing `--cov-report=html`. + +To run the local tests with coverage HTML reports: + +```Bash +DOMAIN=backend sh ./scripts/test-local.sh --cov-report=html +``` + +To run the tests in a running stack with coverage HTML reports: + +```bash +docker compose exec backend bash /app/tests-start.sh --cov-report=html +``` + +### Migrations + +As during local development your app directory is mounted as a volume inside the container, you can also run the migrations with `alembic` commands inside the container and the migration code will be in your app directory (instead of being only inside the container). So you can add it to your git repository. + +Make sure you create a "revision" of your models and that you "upgrade" your database with that revision every time you change them. As this is what will update the tables in your database. Otherwise, your application will have errors. + +* Start an interactive session in the backend container: + +```console +$ docker compose exec backend bash +``` + +* Alembic is already configured to import your SQLModel models from `./backend/app/models.py`. + +* After changing a model (for example, adding a column), inside the container, create a revision, e.g.: + +```console +$ alembic revision --autogenerate -m "Add column last_name to User model" +``` + +* Commit to the git repository the files generated in the alembic directory. + +* After creating the revision, run the migration in the database (this is what will actually change the database): + +```console +$ alembic upgrade head +``` + +If you don't want to use migrations at all, uncomment the lines in the file at `./backend/app/db/init_db.py` that end in: + +```python +SQLModel.metadata.create_all(engine) +``` + +and comment the line in the file `prestart.sh` that contains: + +```console +$ alembic upgrade head +``` + +If you don't want to start with the default models and want to remove them / modify them, from the beginning, without having any previous revision, you can remove the revision files (`.py` Python files) under `./backend/app/alembic/versions/`. And then create a first migration as described above. + +### Development in `localhost` with a custom domain + +You might want to use something different than `localhost` as the domain. For example, if you are having problems with cookies that need a subdomain, and Chrome is not allowing you to use `localhost`. + +In that case, you have two options: you could use the instructions to modify your system `hosts` file with the instructions below in **Development with a custom IP** or you can just use `localhost.tiangolo.com`, it is set up to point to `localhost` (to the IP `127.0.0.1`) and all its subdomains too. And as it is an actual domain, the browsers will store the cookies you set during development, etc. + +If you used the default CORS enabled domains while generating the project, `localhost.tiangolo.com` was configured to be allowed. If you didn't, you will need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. + +To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `localhost.tiangolo.com`. + +After performing those steps you should be able to open: http://localhost.tiangolo.com and it will be served by your stack in `localhost`. + +Check all the corresponding available URLs in the section at the end. + +### Development with a custom IP + +If you are running Docker in an IP address different than `127.0.0.1` (`localhost`), you will need to perform some additional steps. That will be the case if you are running a custom Virtual Machine or your Docker is located in a different machine in your network. + +In that case, you will need to use a fake local domain (`dev.example.com`) and make your computer think that the domain is is served by the custom IP (e.g. `192.168.99.150`). + +If you have a custom domain like that, you need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. + +* Open your `hosts` file with administrative privileges using a text editor: + * **Note for Windows**: If you are in Windows, open the main Windows menu, search for "notepad", right click on it, and select the option "open as Administrator" or similar. Then click the "File" menu, "Open file", go to the directory `c:\Windows\System32\Drivers\etc\`, select the option to show "All files" instead of only "Text (.txt) files", and open the `hosts` file. + * **Note for Mac and Linux**: Your `hosts` file is probably located at `/etc/hosts`, you can edit it in a terminal running `sudo nano /etc/hosts`. + +* Additional to the contents it might have, add a new line with the custom IP (e.g. `192.168.99.150`) a space character, and your fake local domain: `dev.example.com`. + +The new line might look like: + +``` +192.168.99.150 dev.example.com +``` + +* Save the file. + * **Note for Windows**: Make sure you save the file as "All files", without an extension of `.txt`. By default, Windows tries to add the extension. Make sure the file is saved as is, without extension. + +...that will make your computer think that the fake local domain is served by that custom IP, and when you open that URL in your browser, it will talk directly to your locally running server when it is asked to go to `dev.example.com` and think that it is a remote server while it is actually running in your computer. + +To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `dev.example.com`. + +After performing those steps you should be able to open: http://dev.example.com and it will be server by your stack in `192.168.99.150`. + +Check all the corresponding available URLs in the section at the end. + +### Change the development "domain" + +If you need to use your local stack with a different domain than `localhost`, you need to make sure the domain you use points to the IP where your stack is set up. + +To simplify your Docker Compose setup, for example, so that the API docs (Swagger UI) knows where is your API, you should let it know you are using that domain for development. + +* Open the file located at `./.env`. It would have a line like: + +``` +DOMAIN=localhost +``` + +* Change it to the domain you are going to use, e.g.: + +``` +DOMAIN=localhost.tiangolo.com +``` + +That variable will be used by the Docker Compose files. + +After that, you can restart your stack with: + +```bash +docker compose up -d +``` + +and check all the corresponding available URLs in the section at the end. + +## Frontend development + +* Enter the `frontend` directory, install the NPM packages and start the live server using the `npm` scripts: + +```bash +cd frontend +npm install +npm run dev +``` + +Then open your browser at http://localhost:5173/. + +Notice that this live server is not running inside Docker, it is for local development, and that is the recommended workflow. Once you are happy with your frontend, you can build the frontend Docker image and start it, to test it in a production-like environment. But compiling the image at every change will not be as productive as running the local development server with live reload. + +Check the file `package.json` to see other available options. + +### Removing the frontend + +If you are developing an API-only app and want to remove the frontend, you can do it easily: + +* Remove the `./frontend` directory. +* In the `docker-compose.yml` file, remove the whole service / section `frontend`. +* In the `docker-compose.override.yml` file, remove the whole service / section `frontend`. + +Done, you have a frontend-less (api-only) app. 🤓 + +--- + +If you want, you can also remove the `FRONTEND` environment variables from: + +* `.env` +* `./scripts/*.sh` + +But it would be only to clean them up, leaving them won't really have any effect either way. + +## Deployment + +You can deploy the using Docker Compose with a main Traefik proxy outside handling communication to the outside world and HTTPS certificates. + +And you can use CI (continuous integration) systems to do it automatically. + +But you have to configure a couple things first. + +### Traefik network + +This stack expects the public Traefik network to be named `traefik-public`. + +If you need to use a different Traefik public network name, update it in the `docker-compose.yml` files, in the section: + +```YAML +networks: + traefik-public: + external: true +``` + +Change `traefik-public` to the name of the used Traefik network. And then update it in the file `.env`: + +```bash +TRAEFIK_PUBLIC_NETWORK=traefik-public +``` + +## Docker Compose files and env vars + +There is a main `docker-compose.yml` file with all the configurations that apply to the whole stack, it is used automatically by `docker compose`. + +And there's also a `docker-compose.override.yml` with overrides for development, for example to mount the source code as a volume. It is used automatically by `docker compose` to apply overrides on top of `docker-compose.yml`. + +These Docker Compose files use the `.env` file containing configurations to be injected as environment variables in the containers. + +They also use some additional configurations taken from environment variables set in the scripts before calling the `docker compose` command. + +It is all designed to support several "stages", like development, building, testing, and deployment. Also, allowing the deployment to different environments like staging and production (and you can add more environments very easily). + +They are designed to have the minimum repetition of code and configurations, so that if you need to change something, you have to change it in the minimum amount of places. That's why files use environment variables that get auto-expanded. That way, if for example, you want to use a different domain, you can call the `docker compose` command with a different `DOMAIN` environment variable instead of having to change the domain in several places inside the Docker Compose files. + +Also, if you want to have another deployment environment, say `preprod`, you just have to change environment variables, but you can keep using the same Docker Compose files. + +### The .env file + +The `.env` file is the one that contains all your configurations, generated keys and passwords, etc. + +Depending on your workflow, you could want to exclude it from Git, for example if your project is public. In that case, you would have to make sure to set up a way for your CI tools to obtain it while building or deploying your project. + +One way to do it could be to add each environment variable to your CI/CD system, and updating the `docker-compose.yml` file to read that specific env var instead of reading the `.env` file. + +## URLs + +The production or staging URLs would use these same paths, but with your own domain. + +### Development URLs + +Development URLs, for local development. + +Frontend: http://localhost + +Backend: http://localhost/api/ + +Automatic Interactive Docs (Swagger UI): https://localhost/docs + +Automatic Alternative Docs (ReDoc): https://localhost/redoc + +PGAdmin: http://localhost:5050 + +Flower: http://localhost:5555 + +Traefik UI: http://localhost:8090 + +### Development in localhost with a custom domain URLs + +Development URLs, for local development. + +Frontend: http://localhost.tiangolo.com + +Backend: http://localhost.tiangolo.com/api/ + +Automatic Interactive Docs (Swagger UI): https://localhost.tiangolo.com/docs + +Automatic Alternative Docs (ReDoc): https://localhost.tiangolo.com/redoc + +PGAdmin: http://localhost.tiangolo.com:5050 + +Flower: http://localhost.tiangolo.com:5555 + +Traefik UI: http://localhost.tiangolo.com:8090 diff --git a/img/redoc.png b/img/redoc.png deleted file mode 100644 index 09243741d02ddd026276235169b1284b02f48d58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103288 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
J-dW&W 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�g>>zNgaFG^ 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
    ocRjUh!fVUVAESor&yWCe&e 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 Date: Fri, 1 Mar 2024 20:18:33 +0000 Subject: [PATCH 225/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 8d279a3d0c..ad4c82aedf 100644 --- a/release-notes.md +++ b/release-notes.md @@ -86,6 +86,7 @@ ### Docs +* 📝 Update README. PR [#628](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/628) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action latest-changes and move release notes to independent file. PR [#619](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/619) by [@tiangolo](https://github.com/tiangolo). * 📝 Update internal README and referred files. PR [#613](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/613) by [@tiangolo](https://github.com/tiangolo). * 📝 Update README with in construction notice. PR [#552](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/552) by [@tiangolo](https://github.com/tiangolo). From c12f95f3f6d00d8384692ec091d3291f7d67759e Mon Sep 17 00:00:00 2001 From: Esteban Maya Date: Sat, 2 Mar 2024 05:01:59 -0500 Subject: [PATCH 226/771] =?UTF-8?q?=E2=99=BB=20Refactor=20Python=20folder?= =?UTF-8?q?=20tree=20(#629)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- src/backend/app/api/api_v1/endpoints/__init__.py | 0 src/backend/app/api/deps.py | 2 +- src/backend/app/api/{api_v1/api.py => main.py} | 2 +- src/backend/app/api/{api_v1 => routes}/__init__.py | 0 .../app/api/{api_v1/endpoints => routes}/items.py | 0 .../app/api/{api_v1/endpoints => routes}/login.py | 0 .../app/api/{api_v1/endpoints => routes}/users.py | 0 .../app/api/{api_v1/endpoints => routes}/utils.py | 0 src/backend/app/backend_pre_start.py | 2 +- src/backend/app/celeryworker_pre_start.py | 2 +- src/backend/app/{db/init_db.py => core/db.py} | 9 ++++++--- src/backend/app/db/__init__.py | 0 src/backend/app/db/engine.py | 5 ----- src/backend/app/initial_data.py | 3 +-- src/backend/app/main.py | 2 +- src/backend/app/tests/conftest.py | 3 +-- src/backend/app/tests_pre_start.py | 2 +- 17 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 src/backend/app/api/api_v1/endpoints/__init__.py rename src/backend/app/api/{api_v1/api.py => main.py} (83%) rename src/backend/app/api/{api_v1 => routes}/__init__.py (100%) rename src/backend/app/api/{api_v1/endpoints => routes}/items.py (100%) rename src/backend/app/api/{api_v1/endpoints => routes}/login.py (100%) rename src/backend/app/api/{api_v1/endpoints => routes}/users.py (100%) rename src/backend/app/api/{api_v1/endpoints => routes}/utils.py (100%) rename src/backend/app/{db/init_db.py => core/db.py} (83%) delete mode 100644 src/backend/app/db/__init__.py delete mode 100644 src/backend/app/db/engine.py diff --git a/src/backend/app/api/api_v1/endpoints/__init__.py b/src/backend/app/api/api_v1/endpoints/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/backend/app/api/deps.py b/src/backend/app/api/deps.py index 1810ce0820..b6e05c1776 100644 --- a/src/backend/app/api/deps.py +++ b/src/backend/app/api/deps.py @@ -9,7 +9,7 @@ from app.core import security from app.core.config import settings -from app.db.engine import engine +from app.core.db import engine from app.models import TokenPayload, User reusable_oauth2 = OAuth2PasswordBearer( diff --git a/src/backend/app/api/api_v1/api.py b/src/backend/app/api/main.py similarity index 83% rename from src/backend/app/api/api_v1/api.py rename to src/backend/app/api/main.py index 2163017268..09e0663fc3 100644 --- a/src/backend/app/api/api_v1/api.py +++ b/src/backend/app/api/main.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.api.api_v1.endpoints import items, login, users, utils +from app.api.routes import items, login, users, utils api_router = APIRouter() api_router.include_router(login.router, tags=["login"]) diff --git a/src/backend/app/api/api_v1/__init__.py b/src/backend/app/api/routes/__init__.py similarity index 100% rename from src/backend/app/api/api_v1/__init__.py rename to src/backend/app/api/routes/__init__.py diff --git a/src/backend/app/api/api_v1/endpoints/items.py b/src/backend/app/api/routes/items.py similarity index 100% rename from src/backend/app/api/api_v1/endpoints/items.py rename to src/backend/app/api/routes/items.py diff --git a/src/backend/app/api/api_v1/endpoints/login.py b/src/backend/app/api/routes/login.py similarity index 100% rename from src/backend/app/api/api_v1/endpoints/login.py rename to src/backend/app/api/routes/login.py diff --git a/src/backend/app/api/api_v1/endpoints/users.py b/src/backend/app/api/routes/users.py similarity index 100% rename from src/backend/app/api/api_v1/endpoints/users.py rename to src/backend/app/api/routes/users.py diff --git a/src/backend/app/api/api_v1/endpoints/utils.py b/src/backend/app/api/routes/utils.py similarity index 100% rename from src/backend/app/api/api_v1/endpoints/utils.py rename to src/backend/app/api/routes/utils.py diff --git a/src/backend/app/backend_pre_start.py b/src/backend/app/backend_pre_start.py index b7b0abeddb..1693d257d8 100644 --- a/src/backend/app/backend_pre_start.py +++ b/src/backend/app/backend_pre_start.py @@ -3,7 +3,7 @@ from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed -from app.db.engine import engine +from app.core.db import engine logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/src/backend/app/celeryworker_pre_start.py b/src/backend/app/celeryworker_pre_start.py index 3861908762..a9336023b3 100644 --- a/src/backend/app/celeryworker_pre_start.py +++ b/src/backend/app/celeryworker_pre_start.py @@ -3,7 +3,7 @@ from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed -from app.db.engine import engine +from app.core.db import engine logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/src/backend/app/db/init_db.py b/src/backend/app/core/db.py similarity index 83% rename from src/backend/app/db/init_db.py rename to src/backend/app/core/db.py index cb438d19b4..e1c5c22654 100644 --- a/src/backend/app/db/init_db.py +++ b/src/backend/app/core/db.py @@ -1,8 +1,11 @@ -from sqlmodel import Session, select +from sqlmodel import Session, create_engine, select from app import crud from app.core.config import settings -from app.models import User, UserCreate # noqa: F401 +from app.models import User, UserCreate + +engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI)) + # make sure all SQLModel models are imported (app.models) before initializing DB # otherwise, SQLModel might fail to initialize relationships properly @@ -15,7 +18,7 @@ def init_db(session: Session) -> None: # the tables un-commenting the next lines # from sqlmodel import SQLModel - # from app.db.engine import engine + # from app.core.engine import engine # This works because the models are already imported and registered from app.models # SQLModel.metadata.create_all(engine) diff --git a/src/backend/app/db/__init__.py b/src/backend/app/db/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/backend/app/db/engine.py b/src/backend/app/db/engine.py deleted file mode 100644 index 0104294ce5..0000000000 --- a/src/backend/app/db/engine.py +++ /dev/null @@ -1,5 +0,0 @@ -from sqlmodel import create_engine - -from app.core.config import settings - -engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI)) diff --git a/src/backend/app/initial_data.py b/src/backend/app/initial_data.py index dce56efe79..d806c3d381 100644 --- a/src/backend/app/initial_data.py +++ b/src/backend/app/initial_data.py @@ -2,8 +2,7 @@ from sqlmodel import Session -from app.db.engine import engine -from app.db.init_db import init_db +from app.core.db import engine, init_db logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/src/backend/app/main.py b/src/backend/app/main.py index 9722c944f9..e68508b225 100644 --- a/src/backend/app/main.py +++ b/src/backend/app/main.py @@ -2,7 +2,7 @@ from fastapi.routing import APIRoute from starlette.middleware.cors import CORSMiddleware -from app.api.api_v1.api import api_router +from app.api.main import api_router from app.core.config import settings diff --git a/src/backend/app/tests/conftest.py b/src/backend/app/tests/conftest.py index 74a0fb0cca..502a45049a 100644 --- a/src/backend/app/tests/conftest.py +++ b/src/backend/app/tests/conftest.py @@ -5,8 +5,7 @@ from sqlmodel import Session, delete from app.core.config import settings -from app.db.engine import engine -from app.db.init_db import init_db +from app.core.db import engine, init_db from app.main import app from app.models import Item, User from app.tests.utils.user import authentication_token_from_email diff --git a/src/backend/app/tests_pre_start.py b/src/backend/app/tests_pre_start.py index 3861908762..a9336023b3 100644 --- a/src/backend/app/tests_pre_start.py +++ b/src/backend/app/tests_pre_start.py @@ -3,7 +3,7 @@ from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed -from app.db.engine import engine +from app.core.db import engine logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) From 5814bd69ec37658df608ac7baa0d3e53ed5b29aa Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Mar 2024 10:02:20 +0000 Subject: [PATCH 227/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index ad4c82aedf..07e7dded9a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -52,6 +52,7 @@ ### Refactors +* ♻ Refactor Python folder tree. PR [#629](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/629) by [@estebanx64](https://github.com/estebanx64). * ♻️ Refactor old CRUD utils and tests. PR [#622](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/622) by [@alejsdev](https://github.com/alejsdev). * 🔧 Update .env to allow local debug for the backend. PR [#618](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/618) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor and update CORS, remove trailing slash from new Pydantic v2. PR [#617](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/617) by [@tiangolo](https://github.com/tiangolo). From 26e3cd71aaaa527023f5ebf1200a45f0fd285eef Mon Sep 17 00:00:00 2001 From: Esteban Maya Date: Thu, 7 Mar 2024 11:35:33 -0500 Subject: [PATCH 228/771] =?UTF-8?q?=E2=99=BB=20Move=20project=20source=20f?= =?UTF-8?q?iles=20to=20top=20level=20from=20src,=20update=20Sentry=20depen?= =?UTF-8?q?dency=20(#630)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .../.copier-answers.yml.jinja | 0 {src/.copier => .copier}/update_dotenv.py | 0 .github/workflows/test.yml | 3 -- {src/backend => backend}/.dockerignore | 0 {src/backend => backend}/.gitignore | 0 {src/backend => backend}/alembic.ini | 0 {src/backend => backend}/app/__init__.py | 0 {src/backend => backend}/app/alembic/README | 0 {src/backend => backend}/app/alembic/env.py | 0 .../app/alembic/script.py.mako | 0 .../app/alembic/versions/.keep | 0 .../e2412789c190_initialize_models.py | 0 {src/backend => backend}/app/api/__init__.py | 0 {src/backend => backend}/app/api/deps.py | 0 {src/backend => backend}/app/api/main.py | 0 .../app/api/routes/__init__.py | 0 .../app/api/routes/items.py | 0 .../app/api/routes/login.py | 0 .../app/api/routes/users.py | 0 .../app/api/routes/utils.py | 0 .../app/backend_pre_start.py | 0 .../app/celeryworker_pre_start.py | 0 {src/backend => backend}/app/core/__init__.py | 0 .../app/core/celery_app.py | 0 {src/backend => backend}/app/core/config.py | 0 {src/backend => backend}/app/core/db.py | 0 {src/backend => backend}/app/core/security.py | 0 {src/backend => backend}/app/crud.py | 0 .../email-templates/build/new_account.html | 0 .../email-templates/build/reset_password.html | 0 .../app/email-templates/build/test_email.html | 0 .../app/email-templates/src/new_account.mjml | 0 .../email-templates/src/reset_password.mjml | 0 .../app/email-templates/src/test_email.mjml | 0 {src/backend => backend}/app/initial_data.py | 0 {src/backend => backend}/app/main.py | 0 {src/backend => backend}/app/models.py | 0 .../app/schemas/__init__.py | 2 +- {src/backend => backend}/app/schemas/item.py | 0 {src/backend => backend}/app/schemas/msg.py | 0 {src/backend => backend}/app/schemas/token.py | 0 {src/backend => backend}/app/schemas/user.py | 0 .../backend => backend}/app/tests/__init__.py | 0 .../app/tests/api/__init__.py | 0 .../app/tests/api/api_v1/__init__.py | 0 .../app/tests/api/api_v1/test_celery.py | 0 .../app/tests/api/api_v1/test_items.py | 0 .../app/tests/api/api_v1/test_login.py | 0 .../app/tests/api/api_v1/test_users.py | 0 .../backend => backend}/app/tests/conftest.py | 0 .../app/tests/crud/__init__.py | 0 .../app/tests/crud/test_user.py | 0 .../app/tests/utils/__init__.py | 0 .../app/tests/utils/item.py | 0 .../app/tests/utils/user.py | 0 .../app/tests/utils/utils.py | 0 .../app/tests_pre_start.py | 0 {src/backend => backend}/app/utils.py | 0 {src/backend => backend}/app/worker.py | 4 +- {src/backend => backend}/backend.dockerfile | 0 .../celeryworker.dockerfile | 0 {src/backend => backend}/prestart.sh | 0 {src/backend => backend}/pyproject.toml | 1 + .../scripts/format-imports.sh | 0 {src/backend => backend}/scripts/format.sh | 0 {src/backend => backend}/scripts/lint.sh | 0 .../scripts/test-cov-html.sh | 0 {src/backend => backend}/scripts/test.sh | 0 {src/backend => backend}/tests-start.sh | 0 {src/backend => backend}/worker-start.sh | 0 src/copier.yml => copier.yml | 0 ...verride.yml => docker-compose.override.yml | 0 src/docker-compose.yml => docker-compose.yml | 0 {src/frontend => frontend}/.dockerignore | 0 {src/frontend => frontend}/.env | 0 {src/frontend => frontend}/.gitignore | 0 {src/frontend => frontend}/.nvmrc | 0 {src/frontend => frontend}/Dockerfile | 0 {src/frontend => frontend}/README.md | 0 {src/frontend => frontend}/babel.config.js | 0 .../nginx-backend-not-found.conf | 0 {src/frontend => frontend}/nginx.conf | 0 {src/frontend => frontend}/package.json | 0 {src/frontend => frontend}/public/favicon.ico | Bin .../img/icons/android-chrome-192x192.png | Bin .../img/icons/android-chrome-512x512.png | Bin .../img/icons/apple-touch-icon-120x120.png | Bin .../img/icons/apple-touch-icon-152x152.png | Bin .../img/icons/apple-touch-icon-180x180.png | Bin .../img/icons/apple-touch-icon-60x60.png | Bin .../img/icons/apple-touch-icon-76x76.png | Bin .../public/img/icons/apple-touch-icon.png | Bin .../public/img/icons/favicon-16x16.png | Bin .../public/img/icons/favicon-32x32.png | Bin .../img/icons/msapplication-icon-144x144.png | Bin .../public/img/icons/mstile-150x150.png | Bin .../public/img/icons/safari-pinned-tab.svg | 0 {src/frontend => frontend}/public/index.html | 0 .../public/manifest.json | 0 {src/frontend => frontend}/public/robots.txt | 0 {src/frontend => frontend}/src/App.vue | 0 {src/frontend => frontend}/src/api.ts | 0 .../frontend => frontend}/src/assets/logo.png | Bin .../src/component-hooks.ts | 0 .../src/components/NotificationsManager.vue | 0 .../src/components/RouterComponent.vue | 0 .../src/components/UploadButton.vue | 0 {src/frontend => frontend}/src/env.ts | 0 .../src/interfaces/index.ts | 0 {src/frontend => frontend}/src/main.ts | 0 .../src/plugins/vee-validate.ts | 0 .../src/plugins/vuetify.ts | 0 .../src/registerServiceWorker.ts | 0 {src/frontend => frontend}/src/router.ts | 0 {src/frontend => frontend}/src/shims-tsx.d.ts | 0 {src/frontend => frontend}/src/shims-vue.d.ts | 0 .../src/store/admin/actions.ts | 0 .../src/store/admin/getters.ts | 0 .../src/store/admin/index.ts | 0 .../src/store/admin/mutations.ts | 0 .../src/store/admin/state.ts | 0 {src/frontend => frontend}/src/store/index.ts | 0 .../src/store/main/actions.ts | 0 .../src/store/main/getters.ts | 0 .../src/store/main/index.ts | 0 .../src/store/main/mutations.ts | 0 .../src/store/main/state.ts | 0 {src/frontend => frontend}/src/store/state.ts | 0 {src/frontend => frontend}/src/utils.ts | 0 .../frontend => frontend}/src/views/Login.vue | 0 .../src/views/PasswordRecovery.vue | 0 .../src/views/ResetPassword.vue | 0 .../src/views/main/Dashboard.vue | 0 .../src/views/main/Main.vue | 0 .../src/views/main/Start.vue | 0 .../src/views/main/admin/Admin.vue | 0 .../src/views/main/admin/AdminUsers.vue | 0 .../src/views/main/admin/CreateUser.vue | 0 .../src/views/main/admin/EditUser.vue | 0 .../src/views/main/profile/UserProfile.vue | 0 .../views/main/profile/UserProfileEdit.vue | 0 .../main/profile/UserProfileEditPassword.vue | 0 .../tests/unit/upload-button.spec.ts | 0 {src/frontend => frontend}/tsconfig.json | 0 {src/frontend => frontend}/tslint.json | 0 {src/frontend => frontend}/vue.config.js | 0 .../.dockerignore | 0 .../.eslintrc.cjs | 0 {src/new-frontend => new-frontend}/.gitignore | 0 {src/new-frontend => new-frontend}/Dockerfile | 0 {src/new-frontend => new-frontend}/README.md | 0 {src/new-frontend => new-frontend}/index.html | 0 .../modify-openapi-operationids.js | 0 .../nginx-backend-not-found.conf | 0 {src/new-frontend => new-frontend}/nginx.conf | 0 .../package-lock.json | 0 .../package.json | 0 .../src/assets/images/fastapi-logo.svg | 0 .../src/assets/images/favicon.png | Bin .../src/client/core/ApiError.ts | 0 .../src/client/core/ApiRequestOptions.ts | 0 .../src/client/core/ApiResult.ts | 0 .../src/client/core/CancelablePromise.ts | 0 .../src/client/core/OpenAPI.ts | 0 .../src/client/core/request.ts | 0 .../src/client/index.ts | 0 .../models/Body_login_login_access_token.ts | 0 .../src/client/models/HTTPValidationError.ts | 0 .../src/client/models/ItemCreate.ts | 0 .../src/client/models/ItemOut.ts | 0 .../src/client/models/ItemUpdate.ts | 0 .../src/client/models/ItemsOut.ts | 0 .../src/client/models/Message.ts | 0 .../src/client/models/NewPassword.ts | 0 .../src/client/models/Token.ts | 0 .../src/client/models/UpdatePassword.ts | 0 .../src/client/models/UserCreate.ts | 0 .../src/client/models/UserCreateOpen.ts | 0 .../src/client/models/UserOut.ts | 0 .../src/client/models/UserUpdate.ts | 0 .../src/client/models/UserUpdateMe.ts | 0 .../src/client/models/UsersOut.ts | 0 .../src/client/models/ValidationError.ts | 0 .../schemas/$Body_login_login_access_token.ts | 0 .../client/schemas/$HTTPValidationError.ts | 0 .../src/client/schemas/$ItemCreate.ts | 0 .../src/client/schemas/$ItemOut.ts | 0 .../src/client/schemas/$ItemUpdate.ts | 0 .../src/client/schemas/$ItemsOut.ts | 0 .../src/client/schemas/$Message.ts | 0 .../src/client/schemas/$NewPassword.ts | 0 .../src/client/schemas/$Token.ts | 0 .../src/client/schemas/$UpdatePassword.ts | 0 .../src/client/schemas/$UserCreate.ts | 0 .../src/client/schemas/$UserCreateOpen.ts | 0 .../src/client/schemas/$UserOut.ts | 0 .../src/client/schemas/$UserUpdate.ts | 0 .../src/client/schemas/$UserUpdateMe.ts | 0 .../src/client/schemas/$UsersOut.ts | 0 .../src/client/schemas/$ValidationError.ts | 0 .../src/client/services/ItemsService.ts | 0 .../src/client/services/LoginService.ts | 0 .../src/client/services/UsersService.ts | 0 .../src/client/services/UtilsService.ts | 0 .../src/components/Admin/AddUser.tsx | 0 .../src/components/Admin/EditUser.tsx | 0 .../src/components/Common/ActionsMenu.tsx | 0 .../src/components/Common/DeleteAlert.tsx | 0 .../src/components/Common/Navbar.tsx | 0 .../src/components/Common/Sidebar.tsx | 0 .../src/components/Common/SidebarItems.tsx | 0 .../src/components/Common/UserMenu.tsx | 0 .../src/components/Items/AddItem.tsx | 0 .../src/components/Items/EditItem.tsx | 0 .../components/UserSettings/Appearance.tsx | 0 .../UserSettings/ChangePassword.tsx | 0 .../components/UserSettings/DeleteAccount.tsx | 0 .../UserSettings/DeleteConfirmation.tsx | 0 .../UserSettings/UserInformation.tsx | 0 .../src/hooks/useAuth.tsx | 0 .../src/hooks/useCustomToast.tsx | 0 .../src/index.css | 0 .../src/main.tsx | 0 .../src/pages/Admin.tsx | 0 .../src/pages/Dashboard.tsx | 0 .../src/pages/ErrorPage.tsx | 0 .../src/pages/Items.tsx | 0 .../src/pages/Layout.tsx | 0 .../src/pages/Login.tsx | 0 .../src/pages/RecoverPassword.tsx | 0 .../src/pages/ResetPassword.tsx | 0 .../src/pages/UserSettings.tsx | 0 .../src/routes/private_route.tsx | 0 .../src/routes/public_route.tsx | 0 .../src/store/items-store.tsx | 0 .../src/store/user-store.tsx | 0 .../src/store/users-store.tsx | 0 .../src/theme.tsx | 0 .../src/vite-env.d.ts | 0 .../tsconfig.json | 0 .../tsconfig.node.json | 0 .../vite.config.ts | 0 {src/scripts => scripts}/build-push.sh | 0 {src/scripts => scripts}/build.sh | 0 {src/scripts => scripts}/deploy.sh | 0 {src/scripts => scripts}/test-local.sh | 0 {src/scripts => scripts}/test.sh | 0 src/.env | 50 ------------------ 248 files changed, 4 insertions(+), 56 deletions(-) rename {src/.copier => .copier}/.copier-answers.yml.jinja (100%) rename {src/.copier => .copier}/update_dotenv.py (100%) rename {src/backend => backend}/.dockerignore (100%) rename {src/backend => backend}/.gitignore (100%) rename {src/backend => backend}/alembic.ini (100%) rename {src/backend => backend}/app/__init__.py (100%) rename {src/backend => backend}/app/alembic/README (100%) rename {src/backend => backend}/app/alembic/env.py (100%) rename {src/backend => backend}/app/alembic/script.py.mako (100%) rename {src/backend => backend}/app/alembic/versions/.keep (100%) rename {src/backend => backend}/app/alembic/versions/e2412789c190_initialize_models.py (100%) rename {src/backend => backend}/app/api/__init__.py (100%) rename {src/backend => backend}/app/api/deps.py (100%) rename {src/backend => backend}/app/api/main.py (100%) rename {src/backend => backend}/app/api/routes/__init__.py (100%) rename {src/backend => backend}/app/api/routes/items.py (100%) rename {src/backend => backend}/app/api/routes/login.py (100%) rename {src/backend => backend}/app/api/routes/users.py (100%) rename {src/backend => backend}/app/api/routes/utils.py (100%) rename {src/backend => backend}/app/backend_pre_start.py (100%) rename {src/backend => backend}/app/celeryworker_pre_start.py (100%) rename {src/backend => backend}/app/core/__init__.py (100%) rename {src/backend => backend}/app/core/celery_app.py (100%) rename {src/backend => backend}/app/core/config.py (100%) rename {src/backend => backend}/app/core/db.py (100%) rename {src/backend => backend}/app/core/security.py (100%) rename {src/backend => backend}/app/crud.py (100%) rename {src/backend => backend}/app/email-templates/build/new_account.html (100%) rename {src/backend => backend}/app/email-templates/build/reset_password.html (100%) rename {src/backend => backend}/app/email-templates/build/test_email.html (100%) rename {src/backend => backend}/app/email-templates/src/new_account.mjml (100%) rename {src/backend => backend}/app/email-templates/src/reset_password.mjml (100%) rename {src/backend => backend}/app/email-templates/src/test_email.mjml (100%) rename {src/backend => backend}/app/initial_data.py (100%) rename {src/backend => backend}/app/main.py (100%) rename {src/backend => backend}/app/models.py (100%) rename {src/backend => backend}/app/schemas/__init__.py (67%) rename {src/backend => backend}/app/schemas/item.py (100%) rename {src/backend => backend}/app/schemas/msg.py (100%) rename {src/backend => backend}/app/schemas/token.py (100%) rename {src/backend => backend}/app/schemas/user.py (100%) rename {src/backend => backend}/app/tests/__init__.py (100%) rename {src/backend => backend}/app/tests/api/__init__.py (100%) rename {src/backend => backend}/app/tests/api/api_v1/__init__.py (100%) rename {src/backend => backend}/app/tests/api/api_v1/test_celery.py (100%) rename {src/backend => backend}/app/tests/api/api_v1/test_items.py (100%) rename {src/backend => backend}/app/tests/api/api_v1/test_login.py (100%) rename {src/backend => backend}/app/tests/api/api_v1/test_users.py (100%) rename {src/backend => backend}/app/tests/conftest.py (100%) rename {src/backend => backend}/app/tests/crud/__init__.py (100%) rename {src/backend => backend}/app/tests/crud/test_user.py (100%) rename {src/backend => backend}/app/tests/utils/__init__.py (100%) rename {src/backend => backend}/app/tests/utils/item.py (100%) rename {src/backend => backend}/app/tests/utils/user.py (100%) rename {src/backend => backend}/app/tests/utils/utils.py (100%) rename {src/backend => backend}/app/tests_pre_start.py (100%) rename {src/backend => backend}/app/utils.py (100%) rename {src/backend => backend}/app/worker.py (73%) rename {src/backend => backend}/backend.dockerfile (100%) rename {src/backend => backend}/celeryworker.dockerfile (100%) rename {src/backend => backend}/prestart.sh (100%) rename {src/backend => backend}/pyproject.toml (96%) rename {src/backend => backend}/scripts/format-imports.sh (100%) rename {src/backend => backend}/scripts/format.sh (100%) rename {src/backend => backend}/scripts/lint.sh (100%) rename {src/backend => backend}/scripts/test-cov-html.sh (100%) rename {src/backend => backend}/scripts/test.sh (100%) rename {src/backend => backend}/tests-start.sh (100%) rename {src/backend => backend}/worker-start.sh (100%) rename src/copier.yml => copier.yml (100%) rename src/docker-compose.override.yml => docker-compose.override.yml (100%) rename src/docker-compose.yml => docker-compose.yml (100%) rename {src/frontend => frontend}/.dockerignore (100%) rename {src/frontend => frontend}/.env (100%) rename {src/frontend => frontend}/.gitignore (100%) rename {src/frontend => frontend}/.nvmrc (100%) rename {src/frontend => frontend}/Dockerfile (100%) rename {src/frontend => frontend}/README.md (100%) rename {src/frontend => frontend}/babel.config.js (100%) rename {src/frontend => frontend}/nginx-backend-not-found.conf (100%) rename {src/frontend => frontend}/nginx.conf (100%) rename {src/frontend => frontend}/package.json (100%) rename {src/frontend => frontend}/public/favicon.ico (100%) rename {src/frontend => frontend}/public/img/icons/android-chrome-192x192.png (100%) rename {src/frontend => frontend}/public/img/icons/android-chrome-512x512.png (100%) rename {src/frontend => frontend}/public/img/icons/apple-touch-icon-120x120.png (100%) rename {src/frontend => frontend}/public/img/icons/apple-touch-icon-152x152.png (100%) rename {src/frontend => frontend}/public/img/icons/apple-touch-icon-180x180.png (100%) rename {src/frontend => frontend}/public/img/icons/apple-touch-icon-60x60.png (100%) rename {src/frontend => frontend}/public/img/icons/apple-touch-icon-76x76.png (100%) rename {src/frontend => frontend}/public/img/icons/apple-touch-icon.png (100%) rename {src/frontend => frontend}/public/img/icons/favicon-16x16.png (100%) rename {src/frontend => frontend}/public/img/icons/favicon-32x32.png (100%) rename {src/frontend => frontend}/public/img/icons/msapplication-icon-144x144.png (100%) rename {src/frontend => frontend}/public/img/icons/mstile-150x150.png (100%) rename {src/frontend => frontend}/public/img/icons/safari-pinned-tab.svg (100%) rename {src/frontend => frontend}/public/index.html (100%) rename {src/frontend => frontend}/public/manifest.json (100%) rename {src/frontend => frontend}/public/robots.txt (100%) rename {src/frontend => frontend}/src/App.vue (100%) rename {src/frontend => frontend}/src/api.ts (100%) rename {src/frontend => frontend}/src/assets/logo.png (100%) rename {src/frontend => frontend}/src/component-hooks.ts (100%) rename {src/frontend => frontend}/src/components/NotificationsManager.vue (100%) rename {src/frontend => frontend}/src/components/RouterComponent.vue (100%) rename {src/frontend => frontend}/src/components/UploadButton.vue (100%) rename {src/frontend => frontend}/src/env.ts (100%) rename {src/frontend => frontend}/src/interfaces/index.ts (100%) rename {src/frontend => frontend}/src/main.ts (100%) rename {src/frontend => frontend}/src/plugins/vee-validate.ts (100%) rename {src/frontend => frontend}/src/plugins/vuetify.ts (100%) rename {src/frontend => frontend}/src/registerServiceWorker.ts (100%) rename {src/frontend => frontend}/src/router.ts (100%) rename {src/frontend => frontend}/src/shims-tsx.d.ts (100%) rename {src/frontend => frontend}/src/shims-vue.d.ts (100%) rename {src/frontend => frontend}/src/store/admin/actions.ts (100%) rename {src/frontend => frontend}/src/store/admin/getters.ts (100%) rename {src/frontend => frontend}/src/store/admin/index.ts (100%) rename {src/frontend => frontend}/src/store/admin/mutations.ts (100%) rename {src/frontend => frontend}/src/store/admin/state.ts (100%) rename {src/frontend => frontend}/src/store/index.ts (100%) rename {src/frontend => frontend}/src/store/main/actions.ts (100%) rename {src/frontend => frontend}/src/store/main/getters.ts (100%) rename {src/frontend => frontend}/src/store/main/index.ts (100%) rename {src/frontend => frontend}/src/store/main/mutations.ts (100%) rename {src/frontend => frontend}/src/store/main/state.ts (100%) rename {src/frontend => frontend}/src/store/state.ts (100%) rename {src/frontend => frontend}/src/utils.ts (100%) rename {src/frontend => frontend}/src/views/Login.vue (100%) rename {src/frontend => frontend}/src/views/PasswordRecovery.vue (100%) rename {src/frontend => frontend}/src/views/ResetPassword.vue (100%) rename {src/frontend => frontend}/src/views/main/Dashboard.vue (100%) rename {src/frontend => frontend}/src/views/main/Main.vue (100%) rename {src/frontend => frontend}/src/views/main/Start.vue (100%) rename {src/frontend => frontend}/src/views/main/admin/Admin.vue (100%) rename {src/frontend => frontend}/src/views/main/admin/AdminUsers.vue (100%) rename {src/frontend => frontend}/src/views/main/admin/CreateUser.vue (100%) rename {src/frontend => frontend}/src/views/main/admin/EditUser.vue (100%) rename {src/frontend => frontend}/src/views/main/profile/UserProfile.vue (100%) rename {src/frontend => frontend}/src/views/main/profile/UserProfileEdit.vue (100%) rename {src/frontend => frontend}/src/views/main/profile/UserProfileEditPassword.vue (100%) rename {src/frontend => frontend}/tests/unit/upload-button.spec.ts (100%) rename {src/frontend => frontend}/tsconfig.json (100%) rename {src/frontend => frontend}/tslint.json (100%) rename {src/frontend => frontend}/vue.config.js (100%) rename {src/new-frontend => new-frontend}/.dockerignore (100%) rename {src/new-frontend => new-frontend}/.eslintrc.cjs (100%) rename {src/new-frontend => new-frontend}/.gitignore (100%) rename {src/new-frontend => new-frontend}/Dockerfile (100%) rename {src/new-frontend => new-frontend}/README.md (100%) rename {src/new-frontend => new-frontend}/index.html (100%) rename {src/new-frontend => new-frontend}/modify-openapi-operationids.js (100%) rename {src/new-frontend => new-frontend}/nginx-backend-not-found.conf (100%) rename {src/new-frontend => new-frontend}/nginx.conf (100%) rename {src/new-frontend => new-frontend}/package-lock.json (100%) rename {src/new-frontend => new-frontend}/package.json (100%) rename {src/new-frontend => new-frontend}/src/assets/images/fastapi-logo.svg (100%) rename {src/new-frontend => new-frontend}/src/assets/images/favicon.png (100%) rename {src/new-frontend => new-frontend}/src/client/core/ApiError.ts (100%) rename {src/new-frontend => new-frontend}/src/client/core/ApiRequestOptions.ts (100%) rename {src/new-frontend => new-frontend}/src/client/core/ApiResult.ts (100%) rename {src/new-frontend => new-frontend}/src/client/core/CancelablePromise.ts (100%) rename {src/new-frontend => new-frontend}/src/client/core/OpenAPI.ts (100%) rename {src/new-frontend => new-frontend}/src/client/core/request.ts (100%) rename {src/new-frontend => new-frontend}/src/client/index.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/Body_login_login_access_token.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/HTTPValidationError.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/ItemCreate.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/ItemOut.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/ItemUpdate.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/ItemsOut.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/Message.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/NewPassword.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/Token.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/UpdatePassword.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/UserCreate.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/UserCreateOpen.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/UserOut.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/UserUpdate.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/UserUpdateMe.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/UsersOut.ts (100%) rename {src/new-frontend => new-frontend}/src/client/models/ValidationError.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$Body_login_login_access_token.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$HTTPValidationError.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$ItemCreate.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$ItemOut.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$ItemUpdate.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$ItemsOut.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$Message.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$NewPassword.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$Token.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$UpdatePassword.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$UserCreate.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$UserCreateOpen.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$UserOut.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$UserUpdate.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$UserUpdateMe.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$UsersOut.ts (100%) rename {src/new-frontend => new-frontend}/src/client/schemas/$ValidationError.ts (100%) rename {src/new-frontend => new-frontend}/src/client/services/ItemsService.ts (100%) rename {src/new-frontend => new-frontend}/src/client/services/LoginService.ts (100%) rename {src/new-frontend => new-frontend}/src/client/services/UsersService.ts (100%) rename {src/new-frontend => new-frontend}/src/client/services/UtilsService.ts (100%) rename {src/new-frontend => new-frontend}/src/components/Admin/AddUser.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Admin/EditUser.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Common/ActionsMenu.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Common/DeleteAlert.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Common/Navbar.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Common/Sidebar.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Common/SidebarItems.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Common/UserMenu.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Items/AddItem.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/Items/EditItem.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/UserSettings/Appearance.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/UserSettings/ChangePassword.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/UserSettings/DeleteAccount.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/UserSettings/DeleteConfirmation.tsx (100%) rename {src/new-frontend => new-frontend}/src/components/UserSettings/UserInformation.tsx (100%) rename {src/new-frontend => new-frontend}/src/hooks/useAuth.tsx (100%) rename {src/new-frontend => new-frontend}/src/hooks/useCustomToast.tsx (100%) rename {src/new-frontend => new-frontend}/src/index.css (100%) rename {src/new-frontend => new-frontend}/src/main.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/Admin.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/Dashboard.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/ErrorPage.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/Items.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/Layout.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/Login.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/RecoverPassword.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/ResetPassword.tsx (100%) rename {src/new-frontend => new-frontend}/src/pages/UserSettings.tsx (100%) rename {src/new-frontend => new-frontend}/src/routes/private_route.tsx (100%) rename {src/new-frontend => new-frontend}/src/routes/public_route.tsx (100%) rename {src/new-frontend => new-frontend}/src/store/items-store.tsx (100%) rename {src/new-frontend => new-frontend}/src/store/user-store.tsx (100%) rename {src/new-frontend => new-frontend}/src/store/users-store.tsx (100%) rename {src/new-frontend => new-frontend}/src/theme.tsx (100%) rename {src/new-frontend => new-frontend}/src/vite-env.d.ts (100%) rename {src/new-frontend => new-frontend}/tsconfig.json (100%) rename {src/new-frontend => new-frontend}/tsconfig.node.json (100%) rename {src/new-frontend => new-frontend}/vite.config.ts (100%) rename {src/scripts => scripts}/build-push.sh (100%) rename {src/scripts => scripts}/build.sh (100%) rename {src/scripts => scripts}/deploy.sh (100%) rename {src/scripts => scripts}/test-local.sh (100%) rename {src/scripts => scripts}/test.sh (100%) delete mode 100644 src/.env diff --git a/src/.copier/.copier-answers.yml.jinja b/.copier/.copier-answers.yml.jinja similarity index 100% rename from src/.copier/.copier-answers.yml.jinja rename to .copier/.copier-answers.yml.jinja diff --git a/src/.copier/update_dotenv.py b/.copier/update_dotenv.py similarity index 100% rename from src/.copier/update_dotenv.py rename to .copier/update_dotenv.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 491acb17f7..febd40de88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,9 +13,6 @@ jobs: test: runs-on: ubuntu-latest - defaults: - run: - working-directory: src steps: - name: Checkout uses: actions/checkout@v4 diff --git a/src/backend/.dockerignore b/backend/.dockerignore similarity index 100% rename from src/backend/.dockerignore rename to backend/.dockerignore diff --git a/src/backend/.gitignore b/backend/.gitignore similarity index 100% rename from src/backend/.gitignore rename to backend/.gitignore diff --git a/src/backend/alembic.ini b/backend/alembic.ini similarity index 100% rename from src/backend/alembic.ini rename to backend/alembic.ini diff --git a/src/backend/app/__init__.py b/backend/app/__init__.py similarity index 100% rename from src/backend/app/__init__.py rename to backend/app/__init__.py diff --git a/src/backend/app/alembic/README b/backend/app/alembic/README similarity index 100% rename from src/backend/app/alembic/README rename to backend/app/alembic/README diff --git a/src/backend/app/alembic/env.py b/backend/app/alembic/env.py similarity index 100% rename from src/backend/app/alembic/env.py rename to backend/app/alembic/env.py diff --git a/src/backend/app/alembic/script.py.mako b/backend/app/alembic/script.py.mako similarity index 100% rename from src/backend/app/alembic/script.py.mako rename to backend/app/alembic/script.py.mako diff --git a/src/backend/app/alembic/versions/.keep b/backend/app/alembic/versions/.keep similarity index 100% rename from src/backend/app/alembic/versions/.keep rename to backend/app/alembic/versions/.keep diff --git a/src/backend/app/alembic/versions/e2412789c190_initialize_models.py b/backend/app/alembic/versions/e2412789c190_initialize_models.py similarity index 100% rename from src/backend/app/alembic/versions/e2412789c190_initialize_models.py rename to backend/app/alembic/versions/e2412789c190_initialize_models.py diff --git a/src/backend/app/api/__init__.py b/backend/app/api/__init__.py similarity index 100% rename from src/backend/app/api/__init__.py rename to backend/app/api/__init__.py diff --git a/src/backend/app/api/deps.py b/backend/app/api/deps.py similarity index 100% rename from src/backend/app/api/deps.py rename to backend/app/api/deps.py diff --git a/src/backend/app/api/main.py b/backend/app/api/main.py similarity index 100% rename from src/backend/app/api/main.py rename to backend/app/api/main.py diff --git a/src/backend/app/api/routes/__init__.py b/backend/app/api/routes/__init__.py similarity index 100% rename from src/backend/app/api/routes/__init__.py rename to backend/app/api/routes/__init__.py diff --git a/src/backend/app/api/routes/items.py b/backend/app/api/routes/items.py similarity index 100% rename from src/backend/app/api/routes/items.py rename to backend/app/api/routes/items.py diff --git a/src/backend/app/api/routes/login.py b/backend/app/api/routes/login.py similarity index 100% rename from src/backend/app/api/routes/login.py rename to backend/app/api/routes/login.py diff --git a/src/backend/app/api/routes/users.py b/backend/app/api/routes/users.py similarity index 100% rename from src/backend/app/api/routes/users.py rename to backend/app/api/routes/users.py diff --git a/src/backend/app/api/routes/utils.py b/backend/app/api/routes/utils.py similarity index 100% rename from src/backend/app/api/routes/utils.py rename to backend/app/api/routes/utils.py diff --git a/src/backend/app/backend_pre_start.py b/backend/app/backend_pre_start.py similarity index 100% rename from src/backend/app/backend_pre_start.py rename to backend/app/backend_pre_start.py diff --git a/src/backend/app/celeryworker_pre_start.py b/backend/app/celeryworker_pre_start.py similarity index 100% rename from src/backend/app/celeryworker_pre_start.py rename to backend/app/celeryworker_pre_start.py diff --git a/src/backend/app/core/__init__.py b/backend/app/core/__init__.py similarity index 100% rename from src/backend/app/core/__init__.py rename to backend/app/core/__init__.py diff --git a/src/backend/app/core/celery_app.py b/backend/app/core/celery_app.py similarity index 100% rename from src/backend/app/core/celery_app.py rename to backend/app/core/celery_app.py diff --git a/src/backend/app/core/config.py b/backend/app/core/config.py similarity index 100% rename from src/backend/app/core/config.py rename to backend/app/core/config.py diff --git a/src/backend/app/core/db.py b/backend/app/core/db.py similarity index 100% rename from src/backend/app/core/db.py rename to backend/app/core/db.py diff --git a/src/backend/app/core/security.py b/backend/app/core/security.py similarity index 100% rename from src/backend/app/core/security.py rename to backend/app/core/security.py diff --git a/src/backend/app/crud.py b/backend/app/crud.py similarity index 100% rename from src/backend/app/crud.py rename to backend/app/crud.py diff --git a/src/backend/app/email-templates/build/new_account.html b/backend/app/email-templates/build/new_account.html similarity index 100% rename from src/backend/app/email-templates/build/new_account.html rename to backend/app/email-templates/build/new_account.html diff --git a/src/backend/app/email-templates/build/reset_password.html b/backend/app/email-templates/build/reset_password.html similarity index 100% rename from src/backend/app/email-templates/build/reset_password.html rename to backend/app/email-templates/build/reset_password.html diff --git a/src/backend/app/email-templates/build/test_email.html b/backend/app/email-templates/build/test_email.html similarity index 100% rename from src/backend/app/email-templates/build/test_email.html rename to backend/app/email-templates/build/test_email.html diff --git a/src/backend/app/email-templates/src/new_account.mjml b/backend/app/email-templates/src/new_account.mjml similarity index 100% rename from src/backend/app/email-templates/src/new_account.mjml rename to backend/app/email-templates/src/new_account.mjml diff --git a/src/backend/app/email-templates/src/reset_password.mjml b/backend/app/email-templates/src/reset_password.mjml similarity index 100% rename from src/backend/app/email-templates/src/reset_password.mjml rename to backend/app/email-templates/src/reset_password.mjml diff --git a/src/backend/app/email-templates/src/test_email.mjml b/backend/app/email-templates/src/test_email.mjml similarity index 100% rename from src/backend/app/email-templates/src/test_email.mjml rename to backend/app/email-templates/src/test_email.mjml diff --git a/src/backend/app/initial_data.py b/backend/app/initial_data.py similarity index 100% rename from src/backend/app/initial_data.py rename to backend/app/initial_data.py diff --git a/src/backend/app/main.py b/backend/app/main.py similarity index 100% rename from src/backend/app/main.py rename to backend/app/main.py diff --git a/src/backend/app/models.py b/backend/app/models.py similarity index 100% rename from src/backend/app/models.py rename to backend/app/models.py diff --git a/src/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py similarity index 67% rename from src/backend/app/schemas/__init__.py rename to backend/app/schemas/__init__.py index 6b41593dbb..a6ce47fc01 100644 --- a/src/backend/app/schemas/__init__.py +++ b/backend/app/schemas/__init__.py @@ -1,4 +1,4 @@ from .item import Item, ItemCreate, ItemInDB, ItemUpdate from .msg import Msg from .token import Token, TokenPayload -from .user import User, UserCreate, UserInDB, UserUpdate +from .user import User, UserCreate, UserInDB, UserUpdate \ No newline at end of file diff --git a/src/backend/app/schemas/item.py b/backend/app/schemas/item.py similarity index 100% rename from src/backend/app/schemas/item.py rename to backend/app/schemas/item.py diff --git a/src/backend/app/schemas/msg.py b/backend/app/schemas/msg.py similarity index 100% rename from src/backend/app/schemas/msg.py rename to backend/app/schemas/msg.py diff --git a/src/backend/app/schemas/token.py b/backend/app/schemas/token.py similarity index 100% rename from src/backend/app/schemas/token.py rename to backend/app/schemas/token.py diff --git a/src/backend/app/schemas/user.py b/backend/app/schemas/user.py similarity index 100% rename from src/backend/app/schemas/user.py rename to backend/app/schemas/user.py diff --git a/src/backend/app/tests/__init__.py b/backend/app/tests/__init__.py similarity index 100% rename from src/backend/app/tests/__init__.py rename to backend/app/tests/__init__.py diff --git a/src/backend/app/tests/api/__init__.py b/backend/app/tests/api/__init__.py similarity index 100% rename from src/backend/app/tests/api/__init__.py rename to backend/app/tests/api/__init__.py diff --git a/src/backend/app/tests/api/api_v1/__init__.py b/backend/app/tests/api/api_v1/__init__.py similarity index 100% rename from src/backend/app/tests/api/api_v1/__init__.py rename to backend/app/tests/api/api_v1/__init__.py diff --git a/src/backend/app/tests/api/api_v1/test_celery.py b/backend/app/tests/api/api_v1/test_celery.py similarity index 100% rename from src/backend/app/tests/api/api_v1/test_celery.py rename to backend/app/tests/api/api_v1/test_celery.py diff --git a/src/backend/app/tests/api/api_v1/test_items.py b/backend/app/tests/api/api_v1/test_items.py similarity index 100% rename from src/backend/app/tests/api/api_v1/test_items.py rename to backend/app/tests/api/api_v1/test_items.py diff --git a/src/backend/app/tests/api/api_v1/test_login.py b/backend/app/tests/api/api_v1/test_login.py similarity index 100% rename from src/backend/app/tests/api/api_v1/test_login.py rename to backend/app/tests/api/api_v1/test_login.py diff --git a/src/backend/app/tests/api/api_v1/test_users.py b/backend/app/tests/api/api_v1/test_users.py similarity index 100% rename from src/backend/app/tests/api/api_v1/test_users.py rename to backend/app/tests/api/api_v1/test_users.py diff --git a/src/backend/app/tests/conftest.py b/backend/app/tests/conftest.py similarity index 100% rename from src/backend/app/tests/conftest.py rename to backend/app/tests/conftest.py diff --git a/src/backend/app/tests/crud/__init__.py b/backend/app/tests/crud/__init__.py similarity index 100% rename from src/backend/app/tests/crud/__init__.py rename to backend/app/tests/crud/__init__.py diff --git a/src/backend/app/tests/crud/test_user.py b/backend/app/tests/crud/test_user.py similarity index 100% rename from src/backend/app/tests/crud/test_user.py rename to backend/app/tests/crud/test_user.py diff --git a/src/backend/app/tests/utils/__init__.py b/backend/app/tests/utils/__init__.py similarity index 100% rename from src/backend/app/tests/utils/__init__.py rename to backend/app/tests/utils/__init__.py diff --git a/src/backend/app/tests/utils/item.py b/backend/app/tests/utils/item.py similarity index 100% rename from src/backend/app/tests/utils/item.py rename to backend/app/tests/utils/item.py diff --git a/src/backend/app/tests/utils/user.py b/backend/app/tests/utils/user.py similarity index 100% rename from src/backend/app/tests/utils/user.py rename to backend/app/tests/utils/user.py diff --git a/src/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py similarity index 100% rename from src/backend/app/tests/utils/utils.py rename to backend/app/tests/utils/utils.py diff --git a/src/backend/app/tests_pre_start.py b/backend/app/tests_pre_start.py similarity index 100% rename from src/backend/app/tests_pre_start.py rename to backend/app/tests_pre_start.py diff --git a/src/backend/app/utils.py b/backend/app/utils.py similarity index 100% rename from src/backend/app/utils.py rename to backend/app/utils.py diff --git a/src/backend/app/worker.py b/backend/app/worker.py similarity index 73% rename from src/backend/app/worker.py rename to backend/app/worker.py index 5fea53c961..5e1f7d64e5 100644 --- a/src/backend/app/worker.py +++ b/backend/app/worker.py @@ -1,9 +1,9 @@ -from raven import Client +import sentry_sdk from app.core.celery_app import celery_app from app.core.config import settings -client_sentry = Client(settings.SENTRY_DSN) +sentry_sdk.init(dsn=settings.SENTRY_DSN) @celery_app.task(acks_late=True) diff --git a/src/backend/backend.dockerfile b/backend/backend.dockerfile similarity index 100% rename from src/backend/backend.dockerfile rename to backend/backend.dockerfile diff --git a/src/backend/celeryworker.dockerfile b/backend/celeryworker.dockerfile similarity index 100% rename from src/backend/celeryworker.dockerfile rename to backend/celeryworker.dockerfile diff --git a/src/backend/prestart.sh b/backend/prestart.sh similarity index 100% rename from src/backend/prestart.sh rename to backend/prestart.sh diff --git a/src/backend/pyproject.toml b/backend/pyproject.toml similarity index 96% rename from src/backend/pyproject.toml rename to backend/pyproject.toml index 1694eb869a..1f5bc80eb3 100644 --- a/src/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -26,6 +26,7 @@ sqlmodel = "^0.0.16" # Pin bcrypt until passlib supports the latest bcrypt = "4.0.1" pydantic-settings = "^2.2.1" +sentry-sdk = {extras = ["fastapi"], version = "^1.40.6"} [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" diff --git a/src/backend/scripts/format-imports.sh b/backend/scripts/format-imports.sh similarity index 100% rename from src/backend/scripts/format-imports.sh rename to backend/scripts/format-imports.sh diff --git a/src/backend/scripts/format.sh b/backend/scripts/format.sh similarity index 100% rename from src/backend/scripts/format.sh rename to backend/scripts/format.sh diff --git a/src/backend/scripts/lint.sh b/backend/scripts/lint.sh similarity index 100% rename from src/backend/scripts/lint.sh rename to backend/scripts/lint.sh diff --git a/src/backend/scripts/test-cov-html.sh b/backend/scripts/test-cov-html.sh similarity index 100% rename from src/backend/scripts/test-cov-html.sh rename to backend/scripts/test-cov-html.sh diff --git a/src/backend/scripts/test.sh b/backend/scripts/test.sh similarity index 100% rename from src/backend/scripts/test.sh rename to backend/scripts/test.sh diff --git a/src/backend/tests-start.sh b/backend/tests-start.sh similarity index 100% rename from src/backend/tests-start.sh rename to backend/tests-start.sh diff --git a/src/backend/worker-start.sh b/backend/worker-start.sh similarity index 100% rename from src/backend/worker-start.sh rename to backend/worker-start.sh diff --git a/src/copier.yml b/copier.yml similarity index 100% rename from src/copier.yml rename to copier.yml diff --git a/src/docker-compose.override.yml b/docker-compose.override.yml similarity index 100% rename from src/docker-compose.override.yml rename to docker-compose.override.yml diff --git a/src/docker-compose.yml b/docker-compose.yml similarity index 100% rename from src/docker-compose.yml rename to docker-compose.yml diff --git a/src/frontend/.dockerignore b/frontend/.dockerignore similarity index 100% rename from src/frontend/.dockerignore rename to frontend/.dockerignore diff --git a/src/frontend/.env b/frontend/.env similarity index 100% rename from src/frontend/.env rename to frontend/.env diff --git a/src/frontend/.gitignore b/frontend/.gitignore similarity index 100% rename from src/frontend/.gitignore rename to frontend/.gitignore diff --git a/src/frontend/.nvmrc b/frontend/.nvmrc similarity index 100% rename from src/frontend/.nvmrc rename to frontend/.nvmrc diff --git a/src/frontend/Dockerfile b/frontend/Dockerfile similarity index 100% rename from src/frontend/Dockerfile rename to frontend/Dockerfile diff --git a/src/frontend/README.md b/frontend/README.md similarity index 100% rename from src/frontend/README.md rename to frontend/README.md diff --git a/src/frontend/babel.config.js b/frontend/babel.config.js similarity index 100% rename from src/frontend/babel.config.js rename to frontend/babel.config.js diff --git a/src/frontend/nginx-backend-not-found.conf b/frontend/nginx-backend-not-found.conf similarity index 100% rename from src/frontend/nginx-backend-not-found.conf rename to frontend/nginx-backend-not-found.conf diff --git a/src/frontend/nginx.conf b/frontend/nginx.conf similarity index 100% rename from src/frontend/nginx.conf rename to frontend/nginx.conf diff --git a/src/frontend/package.json b/frontend/package.json similarity index 100% rename from src/frontend/package.json rename to frontend/package.json diff --git a/src/frontend/public/favicon.ico b/frontend/public/favicon.ico similarity index 100% rename from src/frontend/public/favicon.ico rename to frontend/public/favicon.ico diff --git a/src/frontend/public/img/icons/android-chrome-192x192.png b/frontend/public/img/icons/android-chrome-192x192.png similarity index 100% rename from src/frontend/public/img/icons/android-chrome-192x192.png rename to frontend/public/img/icons/android-chrome-192x192.png diff --git a/src/frontend/public/img/icons/android-chrome-512x512.png b/frontend/public/img/icons/android-chrome-512x512.png similarity index 100% rename from src/frontend/public/img/icons/android-chrome-512x512.png rename to frontend/public/img/icons/android-chrome-512x512.png diff --git a/src/frontend/public/img/icons/apple-touch-icon-120x120.png b/frontend/public/img/icons/apple-touch-icon-120x120.png similarity index 100% rename from src/frontend/public/img/icons/apple-touch-icon-120x120.png rename to frontend/public/img/icons/apple-touch-icon-120x120.png diff --git a/src/frontend/public/img/icons/apple-touch-icon-152x152.png b/frontend/public/img/icons/apple-touch-icon-152x152.png similarity index 100% rename from src/frontend/public/img/icons/apple-touch-icon-152x152.png rename to frontend/public/img/icons/apple-touch-icon-152x152.png diff --git a/src/frontend/public/img/icons/apple-touch-icon-180x180.png b/frontend/public/img/icons/apple-touch-icon-180x180.png similarity index 100% rename from src/frontend/public/img/icons/apple-touch-icon-180x180.png rename to frontend/public/img/icons/apple-touch-icon-180x180.png diff --git a/src/frontend/public/img/icons/apple-touch-icon-60x60.png b/frontend/public/img/icons/apple-touch-icon-60x60.png similarity index 100% rename from src/frontend/public/img/icons/apple-touch-icon-60x60.png rename to frontend/public/img/icons/apple-touch-icon-60x60.png diff --git a/src/frontend/public/img/icons/apple-touch-icon-76x76.png b/frontend/public/img/icons/apple-touch-icon-76x76.png similarity index 100% rename from src/frontend/public/img/icons/apple-touch-icon-76x76.png rename to frontend/public/img/icons/apple-touch-icon-76x76.png diff --git a/src/frontend/public/img/icons/apple-touch-icon.png b/frontend/public/img/icons/apple-touch-icon.png similarity index 100% rename from src/frontend/public/img/icons/apple-touch-icon.png rename to frontend/public/img/icons/apple-touch-icon.png diff --git a/src/frontend/public/img/icons/favicon-16x16.png b/frontend/public/img/icons/favicon-16x16.png similarity index 100% rename from src/frontend/public/img/icons/favicon-16x16.png rename to frontend/public/img/icons/favicon-16x16.png diff --git a/src/frontend/public/img/icons/favicon-32x32.png b/frontend/public/img/icons/favicon-32x32.png similarity index 100% rename from src/frontend/public/img/icons/favicon-32x32.png rename to frontend/public/img/icons/favicon-32x32.png diff --git a/src/frontend/public/img/icons/msapplication-icon-144x144.png b/frontend/public/img/icons/msapplication-icon-144x144.png similarity index 100% rename from src/frontend/public/img/icons/msapplication-icon-144x144.png rename to frontend/public/img/icons/msapplication-icon-144x144.png diff --git a/src/frontend/public/img/icons/mstile-150x150.png b/frontend/public/img/icons/mstile-150x150.png similarity index 100% rename from src/frontend/public/img/icons/mstile-150x150.png rename to frontend/public/img/icons/mstile-150x150.png diff --git a/src/frontend/public/img/icons/safari-pinned-tab.svg b/frontend/public/img/icons/safari-pinned-tab.svg similarity index 100% rename from src/frontend/public/img/icons/safari-pinned-tab.svg rename to frontend/public/img/icons/safari-pinned-tab.svg diff --git a/src/frontend/public/index.html b/frontend/public/index.html similarity index 100% rename from src/frontend/public/index.html rename to frontend/public/index.html diff --git a/src/frontend/public/manifest.json b/frontend/public/manifest.json similarity index 100% rename from src/frontend/public/manifest.json rename to frontend/public/manifest.json diff --git a/src/frontend/public/robots.txt b/frontend/public/robots.txt similarity index 100% rename from src/frontend/public/robots.txt rename to frontend/public/robots.txt diff --git a/src/frontend/src/App.vue b/frontend/src/App.vue similarity index 100% rename from src/frontend/src/App.vue rename to frontend/src/App.vue diff --git a/src/frontend/src/api.ts b/frontend/src/api.ts similarity index 100% rename from src/frontend/src/api.ts rename to frontend/src/api.ts diff --git a/src/frontend/src/assets/logo.png b/frontend/src/assets/logo.png similarity index 100% rename from src/frontend/src/assets/logo.png rename to frontend/src/assets/logo.png diff --git a/src/frontend/src/component-hooks.ts b/frontend/src/component-hooks.ts similarity index 100% rename from src/frontend/src/component-hooks.ts rename to frontend/src/component-hooks.ts diff --git a/src/frontend/src/components/NotificationsManager.vue b/frontend/src/components/NotificationsManager.vue similarity index 100% rename from src/frontend/src/components/NotificationsManager.vue rename to frontend/src/components/NotificationsManager.vue diff --git a/src/frontend/src/components/RouterComponent.vue b/frontend/src/components/RouterComponent.vue similarity index 100% rename from src/frontend/src/components/RouterComponent.vue rename to frontend/src/components/RouterComponent.vue diff --git a/src/frontend/src/components/UploadButton.vue b/frontend/src/components/UploadButton.vue similarity index 100% rename from src/frontend/src/components/UploadButton.vue rename to frontend/src/components/UploadButton.vue diff --git a/src/frontend/src/env.ts b/frontend/src/env.ts similarity index 100% rename from src/frontend/src/env.ts rename to frontend/src/env.ts diff --git a/src/frontend/src/interfaces/index.ts b/frontend/src/interfaces/index.ts similarity index 100% rename from src/frontend/src/interfaces/index.ts rename to frontend/src/interfaces/index.ts diff --git a/src/frontend/src/main.ts b/frontend/src/main.ts similarity index 100% rename from src/frontend/src/main.ts rename to frontend/src/main.ts diff --git a/src/frontend/src/plugins/vee-validate.ts b/frontend/src/plugins/vee-validate.ts similarity index 100% rename from src/frontend/src/plugins/vee-validate.ts rename to frontend/src/plugins/vee-validate.ts diff --git a/src/frontend/src/plugins/vuetify.ts b/frontend/src/plugins/vuetify.ts similarity index 100% rename from src/frontend/src/plugins/vuetify.ts rename to frontend/src/plugins/vuetify.ts diff --git a/src/frontend/src/registerServiceWorker.ts b/frontend/src/registerServiceWorker.ts similarity index 100% rename from src/frontend/src/registerServiceWorker.ts rename to frontend/src/registerServiceWorker.ts diff --git a/src/frontend/src/router.ts b/frontend/src/router.ts similarity index 100% rename from src/frontend/src/router.ts rename to frontend/src/router.ts diff --git a/src/frontend/src/shims-tsx.d.ts b/frontend/src/shims-tsx.d.ts similarity index 100% rename from src/frontend/src/shims-tsx.d.ts rename to frontend/src/shims-tsx.d.ts diff --git a/src/frontend/src/shims-vue.d.ts b/frontend/src/shims-vue.d.ts similarity index 100% rename from src/frontend/src/shims-vue.d.ts rename to frontend/src/shims-vue.d.ts diff --git a/src/frontend/src/store/admin/actions.ts b/frontend/src/store/admin/actions.ts similarity index 100% rename from src/frontend/src/store/admin/actions.ts rename to frontend/src/store/admin/actions.ts diff --git a/src/frontend/src/store/admin/getters.ts b/frontend/src/store/admin/getters.ts similarity index 100% rename from src/frontend/src/store/admin/getters.ts rename to frontend/src/store/admin/getters.ts diff --git a/src/frontend/src/store/admin/index.ts b/frontend/src/store/admin/index.ts similarity index 100% rename from src/frontend/src/store/admin/index.ts rename to frontend/src/store/admin/index.ts diff --git a/src/frontend/src/store/admin/mutations.ts b/frontend/src/store/admin/mutations.ts similarity index 100% rename from src/frontend/src/store/admin/mutations.ts rename to frontend/src/store/admin/mutations.ts diff --git a/src/frontend/src/store/admin/state.ts b/frontend/src/store/admin/state.ts similarity index 100% rename from src/frontend/src/store/admin/state.ts rename to frontend/src/store/admin/state.ts diff --git a/src/frontend/src/store/index.ts b/frontend/src/store/index.ts similarity index 100% rename from src/frontend/src/store/index.ts rename to frontend/src/store/index.ts diff --git a/src/frontend/src/store/main/actions.ts b/frontend/src/store/main/actions.ts similarity index 100% rename from src/frontend/src/store/main/actions.ts rename to frontend/src/store/main/actions.ts diff --git a/src/frontend/src/store/main/getters.ts b/frontend/src/store/main/getters.ts similarity index 100% rename from src/frontend/src/store/main/getters.ts rename to frontend/src/store/main/getters.ts diff --git a/src/frontend/src/store/main/index.ts b/frontend/src/store/main/index.ts similarity index 100% rename from src/frontend/src/store/main/index.ts rename to frontend/src/store/main/index.ts diff --git a/src/frontend/src/store/main/mutations.ts b/frontend/src/store/main/mutations.ts similarity index 100% rename from src/frontend/src/store/main/mutations.ts rename to frontend/src/store/main/mutations.ts diff --git a/src/frontend/src/store/main/state.ts b/frontend/src/store/main/state.ts similarity index 100% rename from src/frontend/src/store/main/state.ts rename to frontend/src/store/main/state.ts diff --git a/src/frontend/src/store/state.ts b/frontend/src/store/state.ts similarity index 100% rename from src/frontend/src/store/state.ts rename to frontend/src/store/state.ts diff --git a/src/frontend/src/utils.ts b/frontend/src/utils.ts similarity index 100% rename from src/frontend/src/utils.ts rename to frontend/src/utils.ts diff --git a/src/frontend/src/views/Login.vue b/frontend/src/views/Login.vue similarity index 100% rename from src/frontend/src/views/Login.vue rename to frontend/src/views/Login.vue diff --git a/src/frontend/src/views/PasswordRecovery.vue b/frontend/src/views/PasswordRecovery.vue similarity index 100% rename from src/frontend/src/views/PasswordRecovery.vue rename to frontend/src/views/PasswordRecovery.vue diff --git a/src/frontend/src/views/ResetPassword.vue b/frontend/src/views/ResetPassword.vue similarity index 100% rename from src/frontend/src/views/ResetPassword.vue rename to frontend/src/views/ResetPassword.vue diff --git a/src/frontend/src/views/main/Dashboard.vue b/frontend/src/views/main/Dashboard.vue similarity index 100% rename from src/frontend/src/views/main/Dashboard.vue rename to frontend/src/views/main/Dashboard.vue diff --git a/src/frontend/src/views/main/Main.vue b/frontend/src/views/main/Main.vue similarity index 100% rename from src/frontend/src/views/main/Main.vue rename to frontend/src/views/main/Main.vue diff --git a/src/frontend/src/views/main/Start.vue b/frontend/src/views/main/Start.vue similarity index 100% rename from src/frontend/src/views/main/Start.vue rename to frontend/src/views/main/Start.vue diff --git a/src/frontend/src/views/main/admin/Admin.vue b/frontend/src/views/main/admin/Admin.vue similarity index 100% rename from src/frontend/src/views/main/admin/Admin.vue rename to frontend/src/views/main/admin/Admin.vue diff --git a/src/frontend/src/views/main/admin/AdminUsers.vue b/frontend/src/views/main/admin/AdminUsers.vue similarity index 100% rename from src/frontend/src/views/main/admin/AdminUsers.vue rename to frontend/src/views/main/admin/AdminUsers.vue diff --git a/src/frontend/src/views/main/admin/CreateUser.vue b/frontend/src/views/main/admin/CreateUser.vue similarity index 100% rename from src/frontend/src/views/main/admin/CreateUser.vue rename to frontend/src/views/main/admin/CreateUser.vue diff --git a/src/frontend/src/views/main/admin/EditUser.vue b/frontend/src/views/main/admin/EditUser.vue similarity index 100% rename from src/frontend/src/views/main/admin/EditUser.vue rename to frontend/src/views/main/admin/EditUser.vue diff --git a/src/frontend/src/views/main/profile/UserProfile.vue b/frontend/src/views/main/profile/UserProfile.vue similarity index 100% rename from src/frontend/src/views/main/profile/UserProfile.vue rename to frontend/src/views/main/profile/UserProfile.vue diff --git a/src/frontend/src/views/main/profile/UserProfileEdit.vue b/frontend/src/views/main/profile/UserProfileEdit.vue similarity index 100% rename from src/frontend/src/views/main/profile/UserProfileEdit.vue rename to frontend/src/views/main/profile/UserProfileEdit.vue diff --git a/src/frontend/src/views/main/profile/UserProfileEditPassword.vue b/frontend/src/views/main/profile/UserProfileEditPassword.vue similarity index 100% rename from src/frontend/src/views/main/profile/UserProfileEditPassword.vue rename to frontend/src/views/main/profile/UserProfileEditPassword.vue diff --git a/src/frontend/tests/unit/upload-button.spec.ts b/frontend/tests/unit/upload-button.spec.ts similarity index 100% rename from src/frontend/tests/unit/upload-button.spec.ts rename to frontend/tests/unit/upload-button.spec.ts diff --git a/src/frontend/tsconfig.json b/frontend/tsconfig.json similarity index 100% rename from src/frontend/tsconfig.json rename to frontend/tsconfig.json diff --git a/src/frontend/tslint.json b/frontend/tslint.json similarity index 100% rename from src/frontend/tslint.json rename to frontend/tslint.json diff --git a/src/frontend/vue.config.js b/frontend/vue.config.js similarity index 100% rename from src/frontend/vue.config.js rename to frontend/vue.config.js diff --git a/src/new-frontend/.dockerignore b/new-frontend/.dockerignore similarity index 100% rename from src/new-frontend/.dockerignore rename to new-frontend/.dockerignore diff --git a/src/new-frontend/.eslintrc.cjs b/new-frontend/.eslintrc.cjs similarity index 100% rename from src/new-frontend/.eslintrc.cjs rename to new-frontend/.eslintrc.cjs diff --git a/src/new-frontend/.gitignore b/new-frontend/.gitignore similarity index 100% rename from src/new-frontend/.gitignore rename to new-frontend/.gitignore diff --git a/src/new-frontend/Dockerfile b/new-frontend/Dockerfile similarity index 100% rename from src/new-frontend/Dockerfile rename to new-frontend/Dockerfile diff --git a/src/new-frontend/README.md b/new-frontend/README.md similarity index 100% rename from src/new-frontend/README.md rename to new-frontend/README.md diff --git a/src/new-frontend/index.html b/new-frontend/index.html similarity index 100% rename from src/new-frontend/index.html rename to new-frontend/index.html diff --git a/src/new-frontend/modify-openapi-operationids.js b/new-frontend/modify-openapi-operationids.js similarity index 100% rename from src/new-frontend/modify-openapi-operationids.js rename to new-frontend/modify-openapi-operationids.js diff --git a/src/new-frontend/nginx-backend-not-found.conf b/new-frontend/nginx-backend-not-found.conf similarity index 100% rename from src/new-frontend/nginx-backend-not-found.conf rename to new-frontend/nginx-backend-not-found.conf diff --git a/src/new-frontend/nginx.conf b/new-frontend/nginx.conf similarity index 100% rename from src/new-frontend/nginx.conf rename to new-frontend/nginx.conf diff --git a/src/new-frontend/package-lock.json b/new-frontend/package-lock.json similarity index 100% rename from src/new-frontend/package-lock.json rename to new-frontend/package-lock.json diff --git a/src/new-frontend/package.json b/new-frontend/package.json similarity index 100% rename from src/new-frontend/package.json rename to new-frontend/package.json diff --git a/src/new-frontend/src/assets/images/fastapi-logo.svg b/new-frontend/src/assets/images/fastapi-logo.svg similarity index 100% rename from src/new-frontend/src/assets/images/fastapi-logo.svg rename to new-frontend/src/assets/images/fastapi-logo.svg diff --git a/src/new-frontend/src/assets/images/favicon.png b/new-frontend/src/assets/images/favicon.png similarity index 100% rename from src/new-frontend/src/assets/images/favicon.png rename to new-frontend/src/assets/images/favicon.png diff --git a/src/new-frontend/src/client/core/ApiError.ts b/new-frontend/src/client/core/ApiError.ts similarity index 100% rename from src/new-frontend/src/client/core/ApiError.ts rename to new-frontend/src/client/core/ApiError.ts diff --git a/src/new-frontend/src/client/core/ApiRequestOptions.ts b/new-frontend/src/client/core/ApiRequestOptions.ts similarity index 100% rename from src/new-frontend/src/client/core/ApiRequestOptions.ts rename to new-frontend/src/client/core/ApiRequestOptions.ts diff --git a/src/new-frontend/src/client/core/ApiResult.ts b/new-frontend/src/client/core/ApiResult.ts similarity index 100% rename from src/new-frontend/src/client/core/ApiResult.ts rename to new-frontend/src/client/core/ApiResult.ts diff --git a/src/new-frontend/src/client/core/CancelablePromise.ts b/new-frontend/src/client/core/CancelablePromise.ts similarity index 100% rename from src/new-frontend/src/client/core/CancelablePromise.ts rename to new-frontend/src/client/core/CancelablePromise.ts diff --git a/src/new-frontend/src/client/core/OpenAPI.ts b/new-frontend/src/client/core/OpenAPI.ts similarity index 100% rename from src/new-frontend/src/client/core/OpenAPI.ts rename to new-frontend/src/client/core/OpenAPI.ts diff --git a/src/new-frontend/src/client/core/request.ts b/new-frontend/src/client/core/request.ts similarity index 100% rename from src/new-frontend/src/client/core/request.ts rename to new-frontend/src/client/core/request.ts diff --git a/src/new-frontend/src/client/index.ts b/new-frontend/src/client/index.ts similarity index 100% rename from src/new-frontend/src/client/index.ts rename to new-frontend/src/client/index.ts diff --git a/src/new-frontend/src/client/models/Body_login_login_access_token.ts b/new-frontend/src/client/models/Body_login_login_access_token.ts similarity index 100% rename from src/new-frontend/src/client/models/Body_login_login_access_token.ts rename to new-frontend/src/client/models/Body_login_login_access_token.ts diff --git a/src/new-frontend/src/client/models/HTTPValidationError.ts b/new-frontend/src/client/models/HTTPValidationError.ts similarity index 100% rename from src/new-frontend/src/client/models/HTTPValidationError.ts rename to new-frontend/src/client/models/HTTPValidationError.ts diff --git a/src/new-frontend/src/client/models/ItemCreate.ts b/new-frontend/src/client/models/ItemCreate.ts similarity index 100% rename from src/new-frontend/src/client/models/ItemCreate.ts rename to new-frontend/src/client/models/ItemCreate.ts diff --git a/src/new-frontend/src/client/models/ItemOut.ts b/new-frontend/src/client/models/ItemOut.ts similarity index 100% rename from src/new-frontend/src/client/models/ItemOut.ts rename to new-frontend/src/client/models/ItemOut.ts diff --git a/src/new-frontend/src/client/models/ItemUpdate.ts b/new-frontend/src/client/models/ItemUpdate.ts similarity index 100% rename from src/new-frontend/src/client/models/ItemUpdate.ts rename to new-frontend/src/client/models/ItemUpdate.ts diff --git a/src/new-frontend/src/client/models/ItemsOut.ts b/new-frontend/src/client/models/ItemsOut.ts similarity index 100% rename from src/new-frontend/src/client/models/ItemsOut.ts rename to new-frontend/src/client/models/ItemsOut.ts diff --git a/src/new-frontend/src/client/models/Message.ts b/new-frontend/src/client/models/Message.ts similarity index 100% rename from src/new-frontend/src/client/models/Message.ts rename to new-frontend/src/client/models/Message.ts diff --git a/src/new-frontend/src/client/models/NewPassword.ts b/new-frontend/src/client/models/NewPassword.ts similarity index 100% rename from src/new-frontend/src/client/models/NewPassword.ts rename to new-frontend/src/client/models/NewPassword.ts diff --git a/src/new-frontend/src/client/models/Token.ts b/new-frontend/src/client/models/Token.ts similarity index 100% rename from src/new-frontend/src/client/models/Token.ts rename to new-frontend/src/client/models/Token.ts diff --git a/src/new-frontend/src/client/models/UpdatePassword.ts b/new-frontend/src/client/models/UpdatePassword.ts similarity index 100% rename from src/new-frontend/src/client/models/UpdatePassword.ts rename to new-frontend/src/client/models/UpdatePassword.ts diff --git a/src/new-frontend/src/client/models/UserCreate.ts b/new-frontend/src/client/models/UserCreate.ts similarity index 100% rename from src/new-frontend/src/client/models/UserCreate.ts rename to new-frontend/src/client/models/UserCreate.ts diff --git a/src/new-frontend/src/client/models/UserCreateOpen.ts b/new-frontend/src/client/models/UserCreateOpen.ts similarity index 100% rename from src/new-frontend/src/client/models/UserCreateOpen.ts rename to new-frontend/src/client/models/UserCreateOpen.ts diff --git a/src/new-frontend/src/client/models/UserOut.ts b/new-frontend/src/client/models/UserOut.ts similarity index 100% rename from src/new-frontend/src/client/models/UserOut.ts rename to new-frontend/src/client/models/UserOut.ts diff --git a/src/new-frontend/src/client/models/UserUpdate.ts b/new-frontend/src/client/models/UserUpdate.ts similarity index 100% rename from src/new-frontend/src/client/models/UserUpdate.ts rename to new-frontend/src/client/models/UserUpdate.ts diff --git a/src/new-frontend/src/client/models/UserUpdateMe.ts b/new-frontend/src/client/models/UserUpdateMe.ts similarity index 100% rename from src/new-frontend/src/client/models/UserUpdateMe.ts rename to new-frontend/src/client/models/UserUpdateMe.ts diff --git a/src/new-frontend/src/client/models/UsersOut.ts b/new-frontend/src/client/models/UsersOut.ts similarity index 100% rename from src/new-frontend/src/client/models/UsersOut.ts rename to new-frontend/src/client/models/UsersOut.ts diff --git a/src/new-frontend/src/client/models/ValidationError.ts b/new-frontend/src/client/models/ValidationError.ts similarity index 100% rename from src/new-frontend/src/client/models/ValidationError.ts rename to new-frontend/src/client/models/ValidationError.ts diff --git a/src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts b/new-frontend/src/client/schemas/$Body_login_login_access_token.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$Body_login_login_access_token.ts rename to new-frontend/src/client/schemas/$Body_login_login_access_token.ts diff --git a/src/new-frontend/src/client/schemas/$HTTPValidationError.ts b/new-frontend/src/client/schemas/$HTTPValidationError.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$HTTPValidationError.ts rename to new-frontend/src/client/schemas/$HTTPValidationError.ts diff --git a/src/new-frontend/src/client/schemas/$ItemCreate.ts b/new-frontend/src/client/schemas/$ItemCreate.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$ItemCreate.ts rename to new-frontend/src/client/schemas/$ItemCreate.ts diff --git a/src/new-frontend/src/client/schemas/$ItemOut.ts b/new-frontend/src/client/schemas/$ItemOut.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$ItemOut.ts rename to new-frontend/src/client/schemas/$ItemOut.ts diff --git a/src/new-frontend/src/client/schemas/$ItemUpdate.ts b/new-frontend/src/client/schemas/$ItemUpdate.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$ItemUpdate.ts rename to new-frontend/src/client/schemas/$ItemUpdate.ts diff --git a/src/new-frontend/src/client/schemas/$ItemsOut.ts b/new-frontend/src/client/schemas/$ItemsOut.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$ItemsOut.ts rename to new-frontend/src/client/schemas/$ItemsOut.ts diff --git a/src/new-frontend/src/client/schemas/$Message.ts b/new-frontend/src/client/schemas/$Message.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$Message.ts rename to new-frontend/src/client/schemas/$Message.ts diff --git a/src/new-frontend/src/client/schemas/$NewPassword.ts b/new-frontend/src/client/schemas/$NewPassword.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$NewPassword.ts rename to new-frontend/src/client/schemas/$NewPassword.ts diff --git a/src/new-frontend/src/client/schemas/$Token.ts b/new-frontend/src/client/schemas/$Token.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$Token.ts rename to new-frontend/src/client/schemas/$Token.ts diff --git a/src/new-frontend/src/client/schemas/$UpdatePassword.ts b/new-frontend/src/client/schemas/$UpdatePassword.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$UpdatePassword.ts rename to new-frontend/src/client/schemas/$UpdatePassword.ts diff --git a/src/new-frontend/src/client/schemas/$UserCreate.ts b/new-frontend/src/client/schemas/$UserCreate.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$UserCreate.ts rename to new-frontend/src/client/schemas/$UserCreate.ts diff --git a/src/new-frontend/src/client/schemas/$UserCreateOpen.ts b/new-frontend/src/client/schemas/$UserCreateOpen.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$UserCreateOpen.ts rename to new-frontend/src/client/schemas/$UserCreateOpen.ts diff --git a/src/new-frontend/src/client/schemas/$UserOut.ts b/new-frontend/src/client/schemas/$UserOut.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$UserOut.ts rename to new-frontend/src/client/schemas/$UserOut.ts diff --git a/src/new-frontend/src/client/schemas/$UserUpdate.ts b/new-frontend/src/client/schemas/$UserUpdate.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$UserUpdate.ts rename to new-frontend/src/client/schemas/$UserUpdate.ts diff --git a/src/new-frontend/src/client/schemas/$UserUpdateMe.ts b/new-frontend/src/client/schemas/$UserUpdateMe.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$UserUpdateMe.ts rename to new-frontend/src/client/schemas/$UserUpdateMe.ts diff --git a/src/new-frontend/src/client/schemas/$UsersOut.ts b/new-frontend/src/client/schemas/$UsersOut.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$UsersOut.ts rename to new-frontend/src/client/schemas/$UsersOut.ts diff --git a/src/new-frontend/src/client/schemas/$ValidationError.ts b/new-frontend/src/client/schemas/$ValidationError.ts similarity index 100% rename from src/new-frontend/src/client/schemas/$ValidationError.ts rename to new-frontend/src/client/schemas/$ValidationError.ts diff --git a/src/new-frontend/src/client/services/ItemsService.ts b/new-frontend/src/client/services/ItemsService.ts similarity index 100% rename from src/new-frontend/src/client/services/ItemsService.ts rename to new-frontend/src/client/services/ItemsService.ts diff --git a/src/new-frontend/src/client/services/LoginService.ts b/new-frontend/src/client/services/LoginService.ts similarity index 100% rename from src/new-frontend/src/client/services/LoginService.ts rename to new-frontend/src/client/services/LoginService.ts diff --git a/src/new-frontend/src/client/services/UsersService.ts b/new-frontend/src/client/services/UsersService.ts similarity index 100% rename from src/new-frontend/src/client/services/UsersService.ts rename to new-frontend/src/client/services/UsersService.ts diff --git a/src/new-frontend/src/client/services/UtilsService.ts b/new-frontend/src/client/services/UtilsService.ts similarity index 100% rename from src/new-frontend/src/client/services/UtilsService.ts rename to new-frontend/src/client/services/UtilsService.ts diff --git a/src/new-frontend/src/components/Admin/AddUser.tsx b/new-frontend/src/components/Admin/AddUser.tsx similarity index 100% rename from src/new-frontend/src/components/Admin/AddUser.tsx rename to new-frontend/src/components/Admin/AddUser.tsx diff --git a/src/new-frontend/src/components/Admin/EditUser.tsx b/new-frontend/src/components/Admin/EditUser.tsx similarity index 100% rename from src/new-frontend/src/components/Admin/EditUser.tsx rename to new-frontend/src/components/Admin/EditUser.tsx diff --git a/src/new-frontend/src/components/Common/ActionsMenu.tsx b/new-frontend/src/components/Common/ActionsMenu.tsx similarity index 100% rename from src/new-frontend/src/components/Common/ActionsMenu.tsx rename to new-frontend/src/components/Common/ActionsMenu.tsx diff --git a/src/new-frontend/src/components/Common/DeleteAlert.tsx b/new-frontend/src/components/Common/DeleteAlert.tsx similarity index 100% rename from src/new-frontend/src/components/Common/DeleteAlert.tsx rename to new-frontend/src/components/Common/DeleteAlert.tsx diff --git a/src/new-frontend/src/components/Common/Navbar.tsx b/new-frontend/src/components/Common/Navbar.tsx similarity index 100% rename from src/new-frontend/src/components/Common/Navbar.tsx rename to new-frontend/src/components/Common/Navbar.tsx diff --git a/src/new-frontend/src/components/Common/Sidebar.tsx b/new-frontend/src/components/Common/Sidebar.tsx similarity index 100% rename from src/new-frontend/src/components/Common/Sidebar.tsx rename to new-frontend/src/components/Common/Sidebar.tsx diff --git a/src/new-frontend/src/components/Common/SidebarItems.tsx b/new-frontend/src/components/Common/SidebarItems.tsx similarity index 100% rename from src/new-frontend/src/components/Common/SidebarItems.tsx rename to new-frontend/src/components/Common/SidebarItems.tsx diff --git a/src/new-frontend/src/components/Common/UserMenu.tsx b/new-frontend/src/components/Common/UserMenu.tsx similarity index 100% rename from src/new-frontend/src/components/Common/UserMenu.tsx rename to new-frontend/src/components/Common/UserMenu.tsx diff --git a/src/new-frontend/src/components/Items/AddItem.tsx b/new-frontend/src/components/Items/AddItem.tsx similarity index 100% rename from src/new-frontend/src/components/Items/AddItem.tsx rename to new-frontend/src/components/Items/AddItem.tsx diff --git a/src/new-frontend/src/components/Items/EditItem.tsx b/new-frontend/src/components/Items/EditItem.tsx similarity index 100% rename from src/new-frontend/src/components/Items/EditItem.tsx rename to new-frontend/src/components/Items/EditItem.tsx diff --git a/src/new-frontend/src/components/UserSettings/Appearance.tsx b/new-frontend/src/components/UserSettings/Appearance.tsx similarity index 100% rename from src/new-frontend/src/components/UserSettings/Appearance.tsx rename to new-frontend/src/components/UserSettings/Appearance.tsx diff --git a/src/new-frontend/src/components/UserSettings/ChangePassword.tsx b/new-frontend/src/components/UserSettings/ChangePassword.tsx similarity index 100% rename from src/new-frontend/src/components/UserSettings/ChangePassword.tsx rename to new-frontend/src/components/UserSettings/ChangePassword.tsx diff --git a/src/new-frontend/src/components/UserSettings/DeleteAccount.tsx b/new-frontend/src/components/UserSettings/DeleteAccount.tsx similarity index 100% rename from src/new-frontend/src/components/UserSettings/DeleteAccount.tsx rename to new-frontend/src/components/UserSettings/DeleteAccount.tsx diff --git a/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx b/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx similarity index 100% rename from src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx rename to new-frontend/src/components/UserSettings/DeleteConfirmation.tsx diff --git a/src/new-frontend/src/components/UserSettings/UserInformation.tsx b/new-frontend/src/components/UserSettings/UserInformation.tsx similarity index 100% rename from src/new-frontend/src/components/UserSettings/UserInformation.tsx rename to new-frontend/src/components/UserSettings/UserInformation.tsx diff --git a/src/new-frontend/src/hooks/useAuth.tsx b/new-frontend/src/hooks/useAuth.tsx similarity index 100% rename from src/new-frontend/src/hooks/useAuth.tsx rename to new-frontend/src/hooks/useAuth.tsx diff --git a/src/new-frontend/src/hooks/useCustomToast.tsx b/new-frontend/src/hooks/useCustomToast.tsx similarity index 100% rename from src/new-frontend/src/hooks/useCustomToast.tsx rename to new-frontend/src/hooks/useCustomToast.tsx diff --git a/src/new-frontend/src/index.css b/new-frontend/src/index.css similarity index 100% rename from src/new-frontend/src/index.css rename to new-frontend/src/index.css diff --git a/src/new-frontend/src/main.tsx b/new-frontend/src/main.tsx similarity index 100% rename from src/new-frontend/src/main.tsx rename to new-frontend/src/main.tsx diff --git a/src/new-frontend/src/pages/Admin.tsx b/new-frontend/src/pages/Admin.tsx similarity index 100% rename from src/new-frontend/src/pages/Admin.tsx rename to new-frontend/src/pages/Admin.tsx diff --git a/src/new-frontend/src/pages/Dashboard.tsx b/new-frontend/src/pages/Dashboard.tsx similarity index 100% rename from src/new-frontend/src/pages/Dashboard.tsx rename to new-frontend/src/pages/Dashboard.tsx diff --git a/src/new-frontend/src/pages/ErrorPage.tsx b/new-frontend/src/pages/ErrorPage.tsx similarity index 100% rename from src/new-frontend/src/pages/ErrorPage.tsx rename to new-frontend/src/pages/ErrorPage.tsx diff --git a/src/new-frontend/src/pages/Items.tsx b/new-frontend/src/pages/Items.tsx similarity index 100% rename from src/new-frontend/src/pages/Items.tsx rename to new-frontend/src/pages/Items.tsx diff --git a/src/new-frontend/src/pages/Layout.tsx b/new-frontend/src/pages/Layout.tsx similarity index 100% rename from src/new-frontend/src/pages/Layout.tsx rename to new-frontend/src/pages/Layout.tsx diff --git a/src/new-frontend/src/pages/Login.tsx b/new-frontend/src/pages/Login.tsx similarity index 100% rename from src/new-frontend/src/pages/Login.tsx rename to new-frontend/src/pages/Login.tsx diff --git a/src/new-frontend/src/pages/RecoverPassword.tsx b/new-frontend/src/pages/RecoverPassword.tsx similarity index 100% rename from src/new-frontend/src/pages/RecoverPassword.tsx rename to new-frontend/src/pages/RecoverPassword.tsx diff --git a/src/new-frontend/src/pages/ResetPassword.tsx b/new-frontend/src/pages/ResetPassword.tsx similarity index 100% rename from src/new-frontend/src/pages/ResetPassword.tsx rename to new-frontend/src/pages/ResetPassword.tsx diff --git a/src/new-frontend/src/pages/UserSettings.tsx b/new-frontend/src/pages/UserSettings.tsx similarity index 100% rename from src/new-frontend/src/pages/UserSettings.tsx rename to new-frontend/src/pages/UserSettings.tsx diff --git a/src/new-frontend/src/routes/private_route.tsx b/new-frontend/src/routes/private_route.tsx similarity index 100% rename from src/new-frontend/src/routes/private_route.tsx rename to new-frontend/src/routes/private_route.tsx diff --git a/src/new-frontend/src/routes/public_route.tsx b/new-frontend/src/routes/public_route.tsx similarity index 100% rename from src/new-frontend/src/routes/public_route.tsx rename to new-frontend/src/routes/public_route.tsx diff --git a/src/new-frontend/src/store/items-store.tsx b/new-frontend/src/store/items-store.tsx similarity index 100% rename from src/new-frontend/src/store/items-store.tsx rename to new-frontend/src/store/items-store.tsx diff --git a/src/new-frontend/src/store/user-store.tsx b/new-frontend/src/store/user-store.tsx similarity index 100% rename from src/new-frontend/src/store/user-store.tsx rename to new-frontend/src/store/user-store.tsx diff --git a/src/new-frontend/src/store/users-store.tsx b/new-frontend/src/store/users-store.tsx similarity index 100% rename from src/new-frontend/src/store/users-store.tsx rename to new-frontend/src/store/users-store.tsx diff --git a/src/new-frontend/src/theme.tsx b/new-frontend/src/theme.tsx similarity index 100% rename from src/new-frontend/src/theme.tsx rename to new-frontend/src/theme.tsx diff --git a/src/new-frontend/src/vite-env.d.ts b/new-frontend/src/vite-env.d.ts similarity index 100% rename from src/new-frontend/src/vite-env.d.ts rename to new-frontend/src/vite-env.d.ts diff --git a/src/new-frontend/tsconfig.json b/new-frontend/tsconfig.json similarity index 100% rename from src/new-frontend/tsconfig.json rename to new-frontend/tsconfig.json diff --git a/src/new-frontend/tsconfig.node.json b/new-frontend/tsconfig.node.json similarity index 100% rename from src/new-frontend/tsconfig.node.json rename to new-frontend/tsconfig.node.json diff --git a/src/new-frontend/vite.config.ts b/new-frontend/vite.config.ts similarity index 100% rename from src/new-frontend/vite.config.ts rename to new-frontend/vite.config.ts diff --git a/src/scripts/build-push.sh b/scripts/build-push.sh similarity index 100% rename from src/scripts/build-push.sh rename to scripts/build-push.sh diff --git a/src/scripts/build.sh b/scripts/build.sh similarity index 100% rename from src/scripts/build.sh rename to scripts/build.sh diff --git a/src/scripts/deploy.sh b/scripts/deploy.sh similarity index 100% rename from src/scripts/deploy.sh rename to scripts/deploy.sh diff --git a/src/scripts/test-local.sh b/scripts/test-local.sh similarity index 100% rename from src/scripts/test-local.sh rename to scripts/test-local.sh diff --git a/src/scripts/test.sh b/scripts/test.sh similarity index 100% rename from src/scripts/test.sh rename to scripts/test.sh diff --git a/src/.env b/src/.env deleted file mode 100644 index e44fb325d0..0000000000 --- a/src/.env +++ /dev/null @@ -1,50 +0,0 @@ -# Update this with your app domain -DOMAIN=localhost -# DOMAIN=localhost.tiangolo.com - -SERVER_HOST=http://localhost - -PROJECT_NAME="FastAPI Project" - -STACK_NAME=fastapi-project - -# Backend -BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com" -SECRET_KEY=changethis -FIRST_SUPERUSER=admin@example.com -FIRST_SUPERUSER_PASSWORD=changethis -SMTP_HOST= -SMTP_USER= -SMTP_PASSWORD= -EMAILS_FROM_EMAIL=info@example.com -SMTP_TLS=True -SMTP_PORT=587 - -USERS_OPEN_REGISTRATION=False - -# Postgres -POSTGRES_SERVER=db -POSTGRES_USER=postgres -POSTGRES_DB=app -POSTGRES_PASSWORD=changethis - -# PgAdmin -PGADMIN_DEFAULT_EMAIL=admin@example.com -PGADMIN_DEFAULT_PASSWORD=changethis -PGADMIN_LISTEN_PORT=5050 - -SENTRY_DSN= - -# Flower -FLOWER_BASIC_AUTH= - -# Traefik -TRAEFIK_PUBLIC_NETWORK=traefik-public -TRAEFIK_TAG=traefik -TRAEFIK_PUBLIC_TAG=traefik-public - -# Configure these with your own Docker registry images -DOCKER_IMAGE_BACKEND=backend -DOCKER_IMAGE_CELERYWORKER=celery -DOCKER_IMAGE_FRONTEND=frontend -DOCKER_IMAGE_NEW_FRONTEND=new-frontend From db3371c7b0873dcf64169a8057ec319fd7dda1ff Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Mar 2024 16:35:51 +0000 Subject: [PATCH 229/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 07e7dded9a..fd94105e90 100644 --- a/release-notes.md +++ b/release-notes.md @@ -52,6 +52,7 @@ ### Refactors +* ♻ Move project source files to top level from src, update Sentry dependency. PR [#630](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/630) by [@estebanx64](https://github.com/estebanx64). * ♻ Refactor Python folder tree. PR [#629](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/629) by [@estebanx64](https://github.com/estebanx64). * ♻️ Refactor old CRUD utils and tests. PR [#622](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/622) by [@alejsdev](https://github.com/alejsdev). * 🔧 Update .env to allow local debug for the backend. PR [#618](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/618) by [@tiangolo](https://github.com/tiangolo). From 90beb297c7d125d9c792ec12546160e8bde24542 Mon Sep 17 00:00:00 2001 From: Esteban Maya Date: Thu, 7 Mar 2024 12:51:14 -0500 Subject: [PATCH 230/771] =?UTF-8?q?=F0=9F=90=9B=20Fix=20copier=20to=20hand?= =?UTF-8?q?le=20string=20vars=20with=20spaces=20in=20quotes=20(#631)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .copier/update_dotenv.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.copier/update_dotenv.py b/.copier/update_dotenv.py index 1eab4c8ffe..6576885626 100644 --- a/.copier/update_dotenv.py +++ b/.copier/update_dotenv.py @@ -14,7 +14,11 @@ for key, value in answers.items(): upper_key = key.upper() if line.startswith(f"{upper_key}="): - new_line = line.replace(line, f"{upper_key}={value}") + if " " in value: + content = f"{upper_key}={value!r}" + else: + content = f"{upper_key}={value}" + new_line = line.replace(line, content) lines.append(new_line) break else: From 1136b02cd81ad74f6ad80de4d1f593e0fe07b57b Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Mar 2024 17:51:31 +0000 Subject: [PATCH 231/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index fd94105e90..933d7e2221 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🐛 Fix copier to handle string vars with spaces in quotes. PR [#631](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/631) by [@estebanx64](https://github.com/estebanx64). * ⬆ Bump tiangolo/issue-manager from 0.2.0 to 0.5.0. PR [#591](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/591) by [@dependabot[bot]](https://github.com/apps/dependabot). * ✨ Upgrade items router with new SQLModel models, simplified logic, and new FastAPI Annotated dependencies. PR [#560](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/560) by [@tiangolo](https://github.com/tiangolo). * ✨ Adopt SQLModel, create models, start using it. PR [#559](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/559) by [@tiangolo](https://github.com/tiangolo). From d4964b60b85ed49f4cf3049022eca6483269206c Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:16:23 +0100 Subject: [PATCH 232/771] =?UTF-8?q?=E2=9C=A8=20Migrate=20to=20TanStack=20Q?= =?UTF-8?q?uery=20(React=20Query)=20and=20TanStack=20Router=20(#637)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- new-frontend/.env | 1 + new-frontend/package-lock.json | 8601 ++++++++++++----- new-frontend/package.json | 6 +- new-frontend/src/components/Admin/AddUser.tsx | 29 +- .../src/components/Admin/EditUser.tsx | 56 +- .../src/components/Common/ActionsMenu.tsx | 11 +- .../src/components/Common/DeleteAlert.tsx | 42 +- new-frontend/src/components/Common/Navbar.tsx | 17 +- .../src/components/Common/NotFound.tsx | 22 + .../src/components/Common/Sidebar.tsx | 14 +- .../src/components/Common/SidebarItems.tsx | 36 +- .../src/components/Common/UserMenu.tsx | 2 +- new-frontend/src/components/Items/AddItem.tsx | 29 +- .../src/components/Items/EditItem.tsx | 48 +- .../UserSettings/ChangePassword.tsx | 54 +- .../UserSettings/DeleteConfirmation.tsx | 32 +- .../UserSettings/UserInformation.tsx | 56 +- .../src/hooks/{useAuth.tsx => useAuth.ts} | 25 +- .../{useCustomToast.tsx => useCustomToast.ts} | 1 + new-frontend/src/main.tsx | 38 +- new-frontend/src/pages/Dashboard.tsx | 22 - new-frontend/src/pages/ErrorPage.tsx | 25 - new-frontend/src/pages/Layout.tsx | 32 - new-frontend/src/routeTree.gen.ts | 118 + new-frontend/src/routes/__root.tsx | 13 + new-frontend/src/routes/_layout.tsx | 38 + .../Admin.tsx => routes/_layout/admin.tsx} | 50 +- new-frontend/src/routes/_layout/index.tsx | 27 + .../Items.tsx => routes/_layout/items.tsx} | 47 +- .../_layout/settings.tsx} | 28 +- .../src/{pages/Login.tsx => routes/login.tsx} | 21 +- new-frontend/src/routes/private_route.tsx | 21 - new-frontend/src/routes/public_route.tsx | 15 - .../recover-password.tsx} | 44 +- .../reset-password.tsx} | 69 +- new-frontend/src/store/items-store.tsx | 36 - new-frontend/src/store/user-store.tsx | 28 - new-frontend/src/store/users-store.tsx | 36 - new-frontend/vite.config.ts | 5 +- 39 files changed, 7052 insertions(+), 2743 deletions(-) create mode 100644 new-frontend/.env create mode 100644 new-frontend/src/components/Common/NotFound.tsx rename new-frontend/src/hooks/{useAuth.tsx => useAuth.ts} (51%) rename new-frontend/src/hooks/{useCustomToast.tsx => useCustomToast.ts} (92%) delete mode 100644 new-frontend/src/pages/Dashboard.tsx delete mode 100644 new-frontend/src/pages/ErrorPage.tsx delete mode 100644 new-frontend/src/pages/Layout.tsx create mode 100644 new-frontend/src/routeTree.gen.ts create mode 100644 new-frontend/src/routes/__root.tsx create mode 100644 new-frontend/src/routes/_layout.tsx rename new-frontend/src/{pages/Admin.tsx => routes/_layout/admin.tsx} (71%) create mode 100644 new-frontend/src/routes/_layout/index.tsx rename new-frontend/src/{pages/Items.tsx => routes/_layout/items.tsx} (65%) rename new-frontend/src/{pages/UserSettings.tsx => routes/_layout/settings.tsx} (58%) rename new-frontend/src/{pages/Login.tsx => routes/login.tsx} (87%) delete mode 100644 new-frontend/src/routes/private_route.tsx delete mode 100644 new-frontend/src/routes/public_route.tsx rename new-frontend/src/{pages/RecoverPassword.tsx => routes/recover-password.tsx} (53%) rename new-frontend/src/{pages/ResetPassword.tsx => routes/reset-password.tsx} (56%) delete mode 100644 new-frontend/src/store/items-store.tsx delete mode 100644 new-frontend/src/store/user-store.tsx delete mode 100644 new-frontend/src/store/users-store.tsx diff --git a/new-frontend/.env b/new-frontend/.env new file mode 100644 index 0000000000..f829bd1979 --- /dev/null +++ b/new-frontend/.env @@ -0,0 +1 @@ +VITE_API_URL=http://localhost diff --git a/new-frontend/package-lock.json b/new-frontend/package-lock.json index 88d810869e..13f87612bf 100644 --- a/new-frontend/package-lock.json +++ b/new-frontend/package-lock.json @@ -8,12 +8,24 @@ "name": "new-frontend", "version": "0.0.0", "dependencies": { + "@chakra-ui/icons": "2.1.1", + "@chakra-ui/react": "2.8.2", + "@emotion/react": "11.11.3", + "@emotion/styled": "11.11.0", + "@tanstack/react-router": "1.19.1", "axios": "1.6.2", "form-data": "4.0.0", + "framer-motion": "10.16.16", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-hook-form": "7.49.3", + "react-icons": "5.0.1", + "react-query": "3.39.3", + "zustand": "4.5.0" }, "devDependencies": { + "@tanstack/router-devtools": "1.19.1", + "@tanstack/router-vite-plugin": "1.19.0", "@types/node": "20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", @@ -49,1236 +61,4041 @@ "js-yaml": "^4.1.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", - "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", - "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", - "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", - "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "engines": { - "node": ">=12" + "node": ">=0.8.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", - "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", - "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", - "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", - "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", - "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", - "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", - "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", - "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", - "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "engines": { - "node": ">=12" + "node": ">=0.8.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", - "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", - "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", - "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", - "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", - "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" + "node_modules/@chakra-ui/accordion": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-2.3.1.tgz", + "integrity": "sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==", + "dependencies": { + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", - "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" + "node_modules/@chakra-ui/alert": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-2.2.2.tgz", + "integrity": "sha512-jHg4LYMRNOJH830ViLuicjb3F+v6iriE/2G5T+Sd0Hna04nukNJ1MxUmBPE+vI22me2dIflfelu2v9wdB6Pojw==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", - "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@chakra-ui/anatomy": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", + "integrity": "sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==" + }, + "node_modules/@chakra-ui/avatar": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-2.3.0.tgz", + "integrity": "sha512-8gKSyLfygnaotbJbDMHDiJoF38OHXUYVme4gGxZ1fLnQEdPVEaIWfH+NndIjOM0z8S+YEFnT9KyGMUtvPrBk3g==", + "dependencies": { + "@chakra-ui/image": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", - "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@chakra-ui/breadcrumb": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-2.2.0.tgz", + "integrity": "sha512-4cWCG24flYBxjruRi4RJREWTGF74L/KzI2CognAW/d/zWR0CjiScuJhf37Am3LFbCySP6WSoyBOtTIoTA4yLEA==", + "dependencies": { + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", - "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@chakra-ui/breakpoint-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.8.tgz", + "integrity": "sha512-Pq32MlEX9fwb5j5xx8s18zJMARNHlQZH2VH1RZgfgRDpp7DcEgtRW5AInfN5CfqdHLO1dGxA7I3MqEuL5JnIsA==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" } }, - "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, + "node_modules/@chakra-ui/button": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-2.1.0.tgz", + "integrity": "sha512-95CplwlRKmmUXkdEp/21VkEWgnwcx2TOBG6NfYlsuLBDHSLlo5FKIiE2oSi4zXc4TLcopGcWPNcm/NDaSC5pvA==", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/card": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/card/-/card-2.2.0.tgz", + "integrity": "sha512-xUB/k5MURj4CtPAhdSoXZidUbm8j3hci9vnc+eZJVDqhDOShNlD6QeniQNRPRys4lWAQLCbFcrwL29C8naDi6g==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "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/@chakra-ui/checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-2.3.2.tgz", + "integrity": "sha512-85g38JIXMEv6M+AcyIGLh7igNtfpAN6KGQFYxY9tBj0eWvWk4NKQxvqqyVta0bSAyIl1rixNIIezNpNWk2iO4g==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/visually-hidden": "2.2.0", + "@zag-js/focus-visible": "0.16.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", - "dev": true, + "node_modules/@chakra-ui/clickable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.1.0.tgz", + "integrity": "sha512-flRA/ClPUGPYabu+/GLREZVZr9j2uyyazCAUHAdrTUEdDYCr31SVGhgh7dgKdtq23bOvAQJpIJjw/0Bs0WvbXw==", "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" + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/close-button": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-2.1.1.tgz", + "integrity": "sha512-gnpENKOanKexswSVpVz7ojZEALl2x5qjLYNqSQGbxz+aP9sOXPfUS56ebyBrre7T7exuWGiFeRwnM0oVeGPaiw==", + "dependencies": { + "@chakra-ui/icon": "3.2.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node_modules/@chakra-ui/color-mode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-2.2.0.tgz", + "integrity": "sha512-niTEA8PALtMWRI9wJ4LL0CSBDo8NBfLNp4GD6/0hstcm3IlbBHTVKxN6HwSaoNYfphDQLxCjT4yG+0BJA5tFpg==", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, + "node_modules/@chakra-ui/control-box": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-2.1.0.tgz", + "integrity": "sha512-gVrRDyXFdMd8E7rulL0SKeoljkLQiPITFnsyMO8EFHNZ+AHt5wK4LIguYVEq88APqAGZGfHFWXr79RYrNiE3Mg==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/counter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.1.0.tgz", + "integrity": "sha512-s6hZAEcWT5zzjNz2JIWUBzRubo9la/oof1W7EKZVVfPYHERnl5e16FmBC79Yfq8p09LQ+aqFKm/etYoJMMgghw==", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, - "engines": { - "node": ">=10.10.0" + "peerDependencies": { + "react": ">=18" } }, - "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" + "node_modules/@chakra-ui/css-reset": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-2.3.0.tgz", + "integrity": "sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==", + "peerDependencies": { + "@emotion/react": ">=10.0.35", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/descendant": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-3.1.0.tgz", + "integrity": "sha512-VxCIAir08g5w27klLyi7PVo8BxhW4tgU/lxQyujkmi4zx7hT9ZdrcQLAted/dAa+aSIZ14S1oV0Q9lGjsAdxUQ==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true + "node_modules/@chakra-ui/dom-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/dom-utils/-/dom-utils-2.1.0.tgz", + "integrity": "sha512-ZmF2qRa1QZ0CMLU8M1zCfmw29DmPNtfjR9iTo74U5FPr3i1aoAh7fbJ4qAlZ197Xw9eAW28tvzQuoVWeL5C7fQ==" }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true + "node_modules/@chakra-ui/editable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-3.1.0.tgz", + "integrity": "sha512-j2JLrUL9wgg4YA6jLlbU88370eCRyor7DZQD9lzpY95tSOXpTljeg3uF9eOmDnCs6fxp3zDWIfkgMm/ExhcGTg==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } }, - "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, + "node_modules/@chakra-ui/event-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/event-utils/-/event-utils-2.0.8.tgz", + "integrity": "sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==" + }, + "node_modules/@chakra-ui/focus-lock": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-2.1.0.tgz", + "integrity": "sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@chakra-ui/dom-utils": "2.1.0", + "react-focus-lock": "^2.9.4" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "react": ">=18" } }, - "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/@chakra-ui/form-control": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-2.2.0.tgz", + "integrity": "sha512-wehLC1t4fafCVJ2RvJQT2jyqsAwX7KymmiGqBu7nQoQz8ApTkGABWpo/QwDh3F/dBLrouHDoOvGmYTqft3Mirw==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "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, + "node_modules/@chakra-ui/hooks": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-2.2.1.tgz", + "integrity": "sha512-RQbTnzl6b1tBjbDPf9zGRo9rf/pQMholsOudTxjy4i9GfTfz6kgp5ValGjQm2z7ng6Z31N1cnjZ1AlSzQ//ZfQ==", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/utils": "2.0.15", + "compute-scroll-into-view": "3.0.3", + "copy-to-clipboard": "3.3.3" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", - "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] + "node_modules/@chakra-ui/icon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.2.0.tgz", + "integrity": "sha512-xxjGLvlX2Ys4H0iHrI16t74rG9EBcpFvJ3Y3B7KMQTrnW34Kf7Da/UC8J67Gtx85mTHW020ml85SVPKORWNNKQ==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz", - "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@chakra-ui/icons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", + "dependencies": { + "@chakra-ui/icon": "3.2.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/image": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", + "integrity": "sha512-bskumBYKLiLMySIWDGcz0+D9Th0jPvmX6xnRMs4o92tT3Od/bW26lahmV2a2Op2ItXeCmRMY+XxJH5Gy1i46VA==", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-2.1.2.tgz", + "integrity": "sha512-GiBbb3EqAA8Ph43yGa6Mc+kUPjh4Spmxp1Pkelr8qtudpc3p2PJOOebLpd90mcqw8UePPa+l6YhhPtp6o0irhw==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/layout": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-2.3.1.tgz", + "integrity": "sha512-nXuZ6WRbq0WdgnRgLw+QuxWAHuhDtVX8ElWqcTK+cSMFg/52eVP47czYBE5F35YhnoW2XBwfNoNgZ7+e8Z01Rg==", + "dependencies": { + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/lazy-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/lazy-utils/-/lazy-utils-2.0.5.tgz", + "integrity": "sha512-UULqw7FBvcckQk2n3iPO56TMJvDsNv0FKZI6PlUNJVaGsPbsYxK/8IQ60vZgaTVPtVcjY6BE+y6zg8u9HOqpyg==" + }, + "node_modules/@chakra-ui/live-region": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-2.1.0.tgz", + "integrity": "sha512-ZOxFXwtaLIsXjqnszYYrVuswBhnIHHP+XIgK1vC6DePKtyK590Wg+0J0slDwThUAd4MSSIUa/nNX84x1GMphWw==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/media-query": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-3.3.0.tgz", + "integrity": "sha512-IsTGgFLoICVoPRp9ykOgqmdMotJG0CnPsKvGQeSFOB/dZfIujdVb14TYxDU4+MURXry1MhJ7LzZhv+Ml7cr8/g==", + "dependencies": { + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/menu": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-2.2.1.tgz", + "integrity": "sha512-lJS7XEObzJxsOwWQh7yfG4H8FzFPRP5hVPN/CL+JzytEINCSBvsCDHrYPQGp7jzpCi8vnTqQQGQe0f8dwnXd2g==", + "dependencies": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-outside-click": "2.2.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/modal": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-2.3.1.tgz", + "integrity": "sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==", + "dependencies": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0", + "aria-hidden": "^1.2.3", + "react-remove-scroll": "^2.5.6" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/number-input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-2.1.2.tgz", + "integrity": "sha512-pfOdX02sqUN0qC2ysuvgVDiws7xZ20XDIlcNhva55Jgm095xjm8eVdIBfNm3SFbSUNxyXvLTW/YQanX74tKmuA==", + "dependencies": { + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-interval": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/number-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-utils/-/number-utils-2.0.7.tgz", + "integrity": "sha512-yOGxBjXNvLTBvQyhMDqGU0Oj26s91mbAlqKHiuw737AXHt0aPllOthVUqQMeaYLwLCjGMg0jtI7JReRzyi94Dg==" + }, + "node_modules/@chakra-ui/object-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/object-utils/-/object-utils-2.1.0.tgz", + "integrity": "sha512-tgIZOgLHaoti5PYGPTwK3t/cqtcycW0owaiOXoZOcpwwX/vlVb+H1jFsQyWiiwQVPt9RkoSLtxzXamx+aHH+bQ==" + }, + "node_modules/@chakra-ui/pin-input": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-2.1.0.tgz", + "integrity": "sha512-x4vBqLStDxJFMt+jdAHHS8jbh294O53CPQJoL4g228P513rHylV/uPscYUHrVJXRxsHfRztQO9k45jjTYaPRMw==", + "dependencies": { + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/popover": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-2.2.1.tgz", + "integrity": "sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==", + "dependencies": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/popper": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-3.1.0.tgz", + "integrity": "sha512-ciDdpdYbeFG7og6/6J8lkTFxsSvwTdMLFkpVylAF6VNC22jssiWfquj2eyD4rJnzkRFPvIWJq8hvbfhsm+AjSg==", + "dependencies": { + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@popperjs/core": "^2.9.3" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/portal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-2.1.0.tgz", + "integrity": "sha512-9q9KWf6SArEcIq1gGofNcFPSWEyl+MfJjEUg/un1SMlQjaROOh3zYr+6JAwvcORiX7tyHosnmWC3d3wI2aPSQg==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/progress": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.2.0.tgz", + "integrity": "sha512-qUXuKbuhN60EzDD9mHR7B67D7p/ZqNS2Aze4Pbl1qGGZfulPW0PY8Rof32qDtttDQBkzQIzFGE8d9QpAemToIQ==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/provider": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-2.4.2.tgz", + "integrity": "sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==", + "dependencies": { + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/utils": "2.0.15" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/radio": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-2.1.2.tgz", + "integrity": "sha512-n10M46wJrMGbonaghvSRnZ9ToTv/q76Szz284gv4QUWvyljQACcGrXIONUnQ3BIwbOfkRqSk7Xl/JgZtVfll+w==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@zag-js/focus-visible": "0.16.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.8.2.tgz", + "integrity": "sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==", + "dependencies": { + "@chakra-ui/accordion": "2.3.1", + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/avatar": "2.3.0", + "@chakra-ui/breadcrumb": "2.2.0", + "@chakra-ui/button": "2.1.0", + "@chakra-ui/card": "2.2.0", + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/control-box": "2.1.0", + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/editable": "3.1.0", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/hooks": "2.2.1", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/image": "2.1.0", + "@chakra-ui/input": "2.1.2", + "@chakra-ui/layout": "2.3.1", + "@chakra-ui/live-region": "2.1.0", + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/menu": "2.2.1", + "@chakra-ui/modal": "2.3.1", + "@chakra-ui/number-input": "2.1.2", + "@chakra-ui/pin-input": "2.1.0", + "@chakra-ui/popover": "2.2.1", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/progress": "2.2.0", + "@chakra-ui/provider": "2.4.2", + "@chakra-ui/radio": "2.1.2", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/select": "2.1.2", + "@chakra-ui/skeleton": "2.1.0", + "@chakra-ui/skip-nav": "2.1.0", + "@chakra-ui/slider": "2.1.0", + "@chakra-ui/spinner": "2.1.0", + "@chakra-ui/stat": "2.1.1", + "@chakra-ui/stepper": "2.3.1", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/switch": "2.1.2", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/table": "2.1.0", + "@chakra-ui/tabs": "3.0.0", + "@chakra-ui/tag": "3.1.1", + "@chakra-ui/textarea": "2.1.2", + "@chakra-ui/theme": "3.3.1", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/toast": "7.0.2", + "@chakra-ui/tooltip": "2.3.1", + "@chakra-ui/transition": "2.1.0", + "@chakra-ui/utils": "2.0.15", + "@chakra-ui/visually-hidden": "2.2.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/react-children-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-children-utils/-/react-children-utils-2.0.6.tgz", + "integrity": "sha512-QVR2RC7QsOsbWwEnq9YduhpqSFnZGvjjGREV8ygKi8ADhXh93C8azLECCUVgRJF2Wc+So1fgxmjLcbZfY2VmBA==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-context": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-context/-/react-context-2.1.0.tgz", + "integrity": "sha512-iahyStvzQ4AOwKwdPReLGfDesGG+vWJfEsn0X/NoGph/SkN+HXtv2sCfYFFR9k7bb+Kvc6YfpLlSuLvKMHi2+w==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-env": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-3.1.0.tgz", + "integrity": "sha512-Vr96GV2LNBth3+IKzr/rq1IcnkXv+MLmwjQH6C8BRtn3sNskgDFD5vLkVXcEhagzZMCh8FR3V/bzZPojBOyNhw==", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-types/-/react-types-2.0.7.tgz", + "integrity": "sha512-12zv2qIZ8EHwiytggtGvo4iLT0APris7T0qaAWqzpUGS0cdUtR8W+V1BJ5Ocq+7tA6dzQ/7+w5hmXih61TuhWQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-animation-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.1.0.tgz", + "integrity": "sha512-CFZkQU3gmDBwhqy0vC1ryf90BVHxVN8cTLpSyCpdmExUEtSEInSCGMydj2fvn7QXsz/za8JNdO2xxgJwxpLMtg==", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-callback-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.1.0.tgz", + "integrity": "sha512-efnJrBtGDa4YaxDzDE90EnKD3Vkh5a1t3w7PhnRQmsphLy3g2UieasoKTlT2Hn118TwDjIv5ZjHJW6HbzXA9wQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-controllable-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.1.0.tgz", + "integrity": "sha512-QR/8fKNokxZUs4PfxjXuwl0fj/d71WPrmLJvEpCTkHjnzu7LnYvzoe2wB867IdooQJL0G1zBxl0Dq+6W1P3jpg==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-disclosure": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.1.0.tgz", + "integrity": "sha512-Ax4pmxA9LBGMyEZJhhUZobg9C0t3qFE4jVF1tGBsrLDcdBeLR9fwOogIPY9Hf0/wqSlAryAimICbr5hkpa5GSw==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-event-listener": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.1.0.tgz", + "integrity": "sha512-U5greryDLS8ISP69DKDsYcsXRtAdnTQT+jjIlRYZ49K/XhUR/AqVZCK5BkR1spTDmO9H8SPhgeNKI70ODuDU/Q==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-focus-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.1.0.tgz", + "integrity": "sha512-xzVboNy7J64xveLcxTIJ3jv+lUJKDwRM7Szwn9tNzUIPD94O3qwjV7DDCUzN2490nSYDF4OBMt/wuDBtaR3kUQ==", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-focus-on-pointer-down": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.1.0.tgz", + "integrity": "sha512-2jzrUZ+aiCG/cfanrolsnSMDykCAbv9EK/4iUyZno6BYb3vziucmvgKuoXbMPAzWNtwUwtuMhkby8rc61Ue+Lg==", + "dependencies": { + "@chakra-ui/react-use-event-listener": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-interval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-interval/-/react-use-interval-2.1.0.tgz", + "integrity": "sha512-8iWj+I/+A0J08pgEXP1J1flcvhLBHkk0ln7ZvGIyXiEyM6XagOTJpwNhiu+Bmk59t3HoV/VyvyJTa+44sEApuw==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-latest-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-latest-ref/-/react-use-latest-ref-2.1.0.tgz", + "integrity": "sha512-m0kxuIYqoYB0va9Z2aW4xP/5b7BzlDeWwyXCH6QpT2PpW3/281L3hLCm1G0eOUcdVlayqrQqOeD6Mglq+5/xoQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-merge-refs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.1.0.tgz", + "integrity": "sha512-lERa6AWF1cjEtWSGjxWTaSMvneccnAVH4V4ozh8SYiN9fSPZLlSG3kNxfNzdFvMEhM7dnP60vynF7WjGdTgQbQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-outside-click": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.2.0.tgz", + "integrity": "sha512-PNX+s/JEaMneijbgAM4iFL+f3m1ga9+6QK0E5Yh4s8KZJQ/bLwZzdhMz8J/+mL+XEXQ5J0N8ivZN28B82N1kNw==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-pan-event": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.1.0.tgz", + "integrity": "sha512-xmL2qOHiXqfcj0q7ZK5s9UjTh4Gz0/gL9jcWPA6GVf+A0Od5imEDa/Vz+533yQKWiNSm1QGrIj0eJAokc7O4fg==", + "dependencies": { + "@chakra-ui/event-utils": "2.0.8", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "framesync": "6.1.2" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-previous": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-previous/-/react-use-previous-2.1.0.tgz", + "integrity": "sha512-pjxGwue1hX8AFcmjZ2XfrQtIJgqbTF3Qs1Dy3d1krC77dEsiCUbQ9GzOBfDc8pfd60DrB5N2tg5JyHbypqh0Sg==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-safe-layout-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.1.0.tgz", + "integrity": "sha512-Knbrrx/bcPwVS1TorFdzrK/zWA8yuU/eaXDkNj24IrKoRlQrSBFarcgAEzlCHtzuhufP3OULPkELTzz91b0tCw==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-size": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-size/-/react-use-size-2.1.0.tgz", + "integrity": "sha512-tbLqrQhbnqOjzTaMlYytp7wY8BW1JpL78iG7Ru1DlV4EWGiAmXFGvtnEt9HftU0NJ0aJyjgymkxfVGI55/1Z4A==", + "dependencies": { + "@zag-js/element-size": "0.10.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-timeout": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-timeout/-/react-use-timeout-2.1.0.tgz", + "integrity": "sha512-cFN0sobKMM9hXUhyCofx3/Mjlzah6ADaEl/AXl5Y+GawB5rgedgAcu2ErAgarEkwvsKdP6c68CKjQ9dmTQlJxQ==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-update-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.1.0.tgz", + "integrity": "sha512-ND4Q23tETaR2Qd3zwCKYOOS1dfssojPLJMLvUtUbW5M9uW1ejYWgGUobeAiOVfSplownG8QYMmHTP86p/v0lbA==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-utils": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-2.0.12.tgz", + "integrity": "sha512-GbSfVb283+YA3kA8w8xWmzbjNWk14uhNpntnipHCftBibl0lxtQ9YqMFQLwuFOO0U2gYVocszqqDWX+XNKq9hw==", + "dependencies": { + "@chakra-ui/utils": "2.0.15" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.1.2.tgz", + "integrity": "sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/shared-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz", + "integrity": "sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==" + }, + "node_modules/@chakra-ui/skeleton": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-2.1.0.tgz", + "integrity": "sha512-JNRuMPpdZGd6zFVKjVQ0iusu3tXAdI29n4ZENYwAJEMf/fN0l12sVeirOxkJ7oEL0yOx2AgEYFSKdbcAgfUsAQ==", + "dependencies": { + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/react-use-previous": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/skip-nav": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skip-nav/-/skip-nav-2.1.0.tgz", + "integrity": "sha512-Hk+FG+vadBSH0/7hwp9LJnLjkO0RPGnx7gBJWI4/SpoJf3e4tZlWYtwGj0toYY4aGKl93jVghuwGbDBEMoHDug==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/slider": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-2.1.0.tgz", + "integrity": "sha512-lUOBcLMCnFZiA/s2NONXhELJh6sY5WtbRykPtclGfynqqOo47lwWJx+VP7xaeuhDOPcWSSecWc9Y1BfPOCz9cQ==", + "dependencies": { + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-pan-event": "2.1.0", + "@chakra-ui/react-use-size": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/spinner": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-2.1.0.tgz", + "integrity": "sha512-hczbnoXt+MMv/d3gE+hjQhmkzLiKuoTo42YhUG7Bs9OSv2lg1fZHW1fGNRFP3wTi6OIbD044U1P9HK+AOgFH3g==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/stat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-2.1.1.tgz", + "integrity": "sha512-LDn0d/LXQNbAn2KaR3F1zivsZCewY4Jsy1qShmfBMKwn6rI8yVlbvu6SiA3OpHS0FhxbsZxQI6HefEoIgtqY6Q==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/stepper": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stepper/-/stepper-2.3.1.tgz", + "integrity": "sha512-ky77lZbW60zYkSXhYz7kbItUpAQfEdycT0Q4bkHLxfqbuiGMf8OmgZOQkOB9uM4v0zPwy2HXhe0vq4Dd0xa55Q==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/styled-system": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.9.2.tgz", + "integrity": "sha512-To/Z92oHpIE+4nk11uVMWqo2GGRS86coeMmjxtpnErmWRdLcp1WVCVRAvn+ZwpLiNR+reWFr2FFqJRsREuZdAg==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/switch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-2.1.2.tgz", + "integrity": "sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==", + "dependencies": { + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/system": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-2.6.2.tgz", + "integrity": "sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==", + "dependencies": { + "@chakra-ui/color-mode": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/utils": "2.0.15", + "react-fast-compare": "3.2.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-2.1.0.tgz", + "integrity": "sha512-o5OrjoHCh5uCLdiUb0Oc0vq9rIAeHSIRScc2ExTC9Qg/uVZl2ygLrjToCaKfaaKl1oQexIeAcZDKvPG8tVkHyQ==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/tabs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-3.0.0.tgz", + "integrity": "sha512-6Mlclp8L9lqXmsGWF5q5gmemZXOiOYuh0SGT/7PgJVNPz3LXREXlXg2an4MBUD8W5oTkduCX+3KTMCwRrVrDYw==", + "dependencies": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/tag": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-3.1.1.tgz", + "integrity": "sha512-Bdel79Dv86Hnge2PKOU+t8H28nm/7Y3cKd4Kfk9k3lOpUh4+nkSGe58dhRzht59lEqa4N9waCgQiBdkydjvBXQ==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/textarea": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-2.1.2.tgz", + "integrity": "sha512-ip7tvklVCZUb2fOHDb23qPy/Fr2mzDOGdkrpbNi50hDCiV4hFX02jdQJdi3ydHZUyVgZVBKPOJ+lT9i7sKA2wA==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/theme": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-3.3.1.tgz", + "integrity": "sha512-Hft/VaT8GYnItGCBbgWd75ICrIrIFrR7lVOhV/dQnqtfGqsVDlrztbSErvMkoPKt0UgAkd9/o44jmZ6X4U2nZQ==", + "dependencies": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.1.2" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.8.0" + } + }, + "node_modules/@chakra-ui/theme-tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-2.1.2.tgz", + "integrity": "sha512-Qdj8ajF9kxY4gLrq7gA+Azp8CtFHGO9tWMN2wfF9aQNgG9AuMhPrUzMq9AMQ0MXiYcgNq/FD3eegB43nHVmXVA==", + "dependencies": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.2" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.0.0" + } + }, + "node_modules/@chakra-ui/theme-utils": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-utils/-/theme-utils-2.0.21.tgz", + "integrity": "sha512-FjH5LJbT794r0+VSCXB3lT4aubI24bLLRWB+CuRKHijRvsOg717bRdUN/N1fEmEpFnRVrbewttWh/OQs0EWpWw==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/toast": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-7.0.2.tgz", + "integrity": "sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==", + "dependencies": { + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-timeout": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1" + }, + "peerDependencies": { + "@chakra-ui/system": "2.6.2", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/tooltip": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-2.3.1.tgz", + "integrity": "sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/transition": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-2.1.0.tgz", + "integrity": "sha512-orkT6T/Dt+/+kVwJNy7zwJ+U2xAZ3EU7M3XCs45RBvUnZDr/u9vdmaM/3D/rOpmQJWgQBwKPJleUXrYWUagEDQ==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "dependencies": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/visually-hidden": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-2.2.0.tgz", + "integrity": "sha512-KmKDg01SrQ7VbTD3+cPWf/UfpF5MSwm3v7MWi0n5t8HnnadT13MF0MJCDSXbBWnzLv1ZKJ6zlyAOeARWX+DpjQ==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", + "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.3.tgz", + "integrity": "sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", + "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", + "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", + "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", + "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", + "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", + "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", + "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", + "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", + "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", + "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", + "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", + "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", + "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", + "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", + "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", + "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", + "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", + "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", + "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", + "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", + "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", + "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "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.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "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/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/@eslint/eslintrc/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/@eslint/js": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.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.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "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/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", + "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz", + "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz", + "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz", + "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz", + "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz", + "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz", + "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", + "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz", + "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz", + "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz", + "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz", + "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.100.tgz", + "integrity": "sha512-7dKgTyxJjlrMwFZYb1auj3Xq0D8ZBe+5oeIgfMlRU05doXZypYJe0LAk0yjj3WdbwYzpF+T1PLxwTWizI0pckw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.100", + "@swc/core-darwin-x64": "1.3.100", + "@swc/core-linux-arm64-gnu": "1.3.100", + "@swc/core-linux-arm64-musl": "1.3.100", + "@swc/core-linux-x64-gnu": "1.3.100", + "@swc/core-linux-x64-musl": "1.3.100", + "@swc/core-win32-arm64-msvc": "1.3.100", + "@swc/core-win32-ia32-msvc": "1.3.100", + "@swc/core-win32-x64-msvc": "1.3.100" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.100.tgz", + "integrity": "sha512-XVWFsKe6ei+SsDbwmsuRkYck1SXRpO60Hioa4hoLwR8fxbA9eVp6enZtMxzVVMBi8ej5seZ4HZQeAWepbukiBw==", + "cpu": [ + "arm64" + ], + "dev": true, "optional": true, "os": [ - "android" - ] + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.100.tgz", + "integrity": "sha512-KF/MXrnH1nakm1wbt4XV8FS7kvqD9TGmVxeJ0U4bbvxXMvzeYUurzg3AJUTXYmXDhH/VXOYJE5N5RkwZZPs5iA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.100.tgz", + "integrity": "sha512-p8hikNnAEJrw5vHCtKiFT4hdlQxk1V7vqPmvUDgL/qe2menQDK/i12tbz7/3BEQ4UqUPnvwpmVn2d19RdEMNxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.100.tgz", + "integrity": "sha512-BWx/0EeY89WC4q3AaIaBSGfQxkYxIlS3mX19dwy2FWJs/O+fMvF9oLk/CyJPOZzbp+1DjGeeoGFuDYpiNO91JA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.100.tgz", + "integrity": "sha512-XUdGu3dxAkjsahLYnm8WijPfKebo+jHgHphDxaW0ovI6sTdmEGFDew7QzKZRlbYL2jRkUuuKuDGvD6lO5frmhA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.100.tgz", + "integrity": "sha512-PhoXKf+f0OaNW/GCuXjJ0/KfK9EJX7z2gko+7nVnEA0p3aaPtbP6cq1Ubbl6CMoPL+Ci3gZ7nYumDqXNc3CtLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.100.tgz", + "integrity": "sha512-PwLADZN6F9cXn4Jw52FeP/MCLVHm8vwouZZSOoOScDtihjY495SSjdPnlosMaRSR4wJQssGwiD/4MbpgQPqbAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.100.tgz", + "integrity": "sha512-0f6nicKSLlDKlyPRl2JEmkpBV4aeDfRQg6n8mPqgL7bliZIcDahG0ej+HxgNjZfS3e0yjDxsNRa6sAqWU2Z60A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.100", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.100.tgz", + "integrity": "sha512-b7J0rPoMkRTa3XyUGt8PwCaIBuYWsL2DqbirrQKRESzgCvif5iNpqaM6kjIjI/5y5q1Ycv564CB51YDpiS8EtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true + }, + "node_modules/@tanstack/history": { + "version": "1.15.13", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.15.13.tgz", + "integrity": "sha512-ToaeMtK5S4YaxCywAlYexc7KPFN0esjyTZ4vXzJhXEWAkro9iHgh7m/4ozPJb7oTo65WkHWX0W9GjcZbInSD8w==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-router": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.19.1.tgz", + "integrity": "sha512-a4Xf074qo2fQLmSi8PTncEFn8XakaH3+DT7Dted4OPClzQFS+c6yU3HONVNAsuYWZ7lDK1HMKoHPDFbnHPEWvA==", + "dependencies": { + "@tanstack/history": "1.15.13", + "@tanstack/react-store": "^0.2.1", + "tiny-invariant": "^1.3.1", + "tiny-warning": "^1.0.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.2.1.tgz", + "integrity": "sha512-tEbMCQjbeVw9KOP/202LfqZMSNAVi6zYkkp1kBom8nFuMx/965Hzes3+6G6b/comCwVxoJU8Gg9IrcF8yRPthw==", + "dependencies": { + "@tanstack/store": "0.1.3", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/router-devtools": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.19.1.tgz", + "integrity": "sha512-l560JHnffcDccSTo/sOtB+gKvtgaWYpOKOu9MyvswN9XB2pt752UFFIN1Yt/Gsp2Iooq/FcYlYnEPHb4GFzalg==", + "dev": true, + "dependencies": { + "@tanstack/react-router": "1.19.1", + "clsx": "^2.1.0", + "date-fns": "^2.29.1", + "goober": "^2.1.14" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/router-generator": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.19.0.tgz", + "integrity": "sha512-vFF8Q7SdyygiYC7lfJ83GRif0vcxjak9SAcgtX/w7TLR0O+qdxRXFPvhKTQQXH6vVezy5Au9bSaSI2EgDD1ubA==", + "dev": true, + "dependencies": { + "prettier": "^3.1.1", + "zod": "^3.22.4" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-vite-plugin": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-vite-plugin/-/router-vite-plugin-1.19.0.tgz", + "integrity": "sha512-yvvQnJ7JvqsnxAFqwiHhNTV2n1jKkidjc+XbgS2aNnEHC0aHnYH2ygPlmmfiVD7PMO7x64PdI5e12TzY/aKoFA==", + "dev": true, + "dependencies": { + "@tanstack/router-generator": "1.19.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/store": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.1.3.tgz", + "integrity": "sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + }, + "node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "devOptional": true + }, + "node_modules/@types/react": { + "version": "18.2.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", + "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", + "devOptional": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", + "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "devOptional": true + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", + "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/type-utils": "6.13.1", + "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", + "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", + "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", + "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/utils": "6.13.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", + "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", + "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", + "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", + "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.13.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "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-react-swc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", + "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==", + "dev": true, + "dependencies": { + "@swc/core": "^1.3.96" + }, + "peerDependencies": { + "vite": "^4 || ^5" + } + }, + "node_modules/@zag-js/dom-query": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.16.0.tgz", + "integrity": "sha512-Oqhd6+biWyKnhKwFFuZrrf6lxBz2tX2pRQe6grUnYwO6HJ8BcbqZomy2lpOdr+3itlaUqx+Ywj5E5ZZDr/LBfQ==" + }, + "node_modules/@zag-js/element-size": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.10.5.tgz", + "integrity": "sha512-uQre5IidULANvVkNOBQ1tfgwTQcGl4hliPSe69Fct1VfYb2Fd0jdAcGzqQgPhfrXFpR62MxLPB7erxJ/ngtL8w==" + }, + "node_modules/@zag-js/focus-visible": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.16.0.tgz", + "integrity": "sha512-a7U/HSopvQbrDU4GLerpqiMcHKEkQkNPeDZJWz38cw/6Upunh41GjHetq5TB84hxyCaDzJ6q2nEdNoBQfC0FKA==", + "dependencies": { + "@zag-js/dom-query": "0.16.0" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "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/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/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/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/aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "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==" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/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/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", + "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz", - "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==", - "cpu": [ - "arm64" - ], + "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, - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz", - "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==", - "cpu": [ - "x64" - ], + "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==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz", - "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==", - "cpu": [ - "arm" - ], + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz", - "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==", - "cpu": [ - "arm64" - ], + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", + "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.8", + "@esbuild/android-arm64": "0.19.8", + "@esbuild/android-x64": "0.19.8", + "@esbuild/darwin-arm64": "0.19.8", + "@esbuild/darwin-x64": "0.19.8", + "@esbuild/freebsd-arm64": "0.19.8", + "@esbuild/freebsd-x64": "0.19.8", + "@esbuild/linux-arm": "0.19.8", + "@esbuild/linux-arm64": "0.19.8", + "@esbuild/linux-ia32": "0.19.8", + "@esbuild/linux-loong64": "0.19.8", + "@esbuild/linux-mips64el": "0.19.8", + "@esbuild/linux-ppc64": "0.19.8", + "@esbuild/linux-riscv64": "0.19.8", + "@esbuild/linux-s390x": "0.19.8", + "@esbuild/linux-x64": "0.19.8", + "@esbuild/netbsd-x64": "0.19.8", + "@esbuild/openbsd-x64": "0.19.8", + "@esbuild/sunos-x64": "0.19.8", + "@esbuild/win32-arm64": "0.19.8", + "@esbuild/win32-ia32": "0.19.8", + "@esbuild/win32-x64": "0.19.8" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz", - "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==", - "cpu": [ - "arm64" - ], + "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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", + "@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/@rollup/rollup-linux-x64-gnu": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", - "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", - "cpu": [ - "x64" - ], + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz", - "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==", - "cpu": [ - "x64" - ], + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.4.tgz", + "integrity": "sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "peerDependencies": { + "eslint": ">=7" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz", - "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==", - "cpu": [ - "arm64" - ], + "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, - "optional": true, - "os": [ - "win32" - ] + "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/@rollup/rollup-win32-ia32-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz", - "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==", - "cpu": [ - "ia32" - ], + "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, - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz", - "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==", - "cpu": [ - "x64" - ], + "node_modules/eslint/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, - "optional": true, - "os": [ - "win32" - ] + "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/@swc/core": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.100.tgz", - "integrity": "sha512-7dKgTyxJjlrMwFZYb1auj3Xq0D8ZBe+5oeIgfMlRU05doXZypYJe0LAk0yjj3WdbwYzpF+T1PLxwTWizI0pckw==", + "node_modules/eslint/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/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, - "hasInstallScript": true, "dependencies": { - "@swc/counter": "^0.1.1", - "@swc/types": "^0.1.5" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.100", - "@swc/core-darwin-x64": "1.3.100", - "@swc/core-linux-arm64-gnu": "1.3.100", - "@swc/core-linux-arm64-musl": "1.3.100", - "@swc/core-linux-x64-gnu": "1.3.100", - "@swc/core-linux-x64-musl": "1.3.100", - "@swc/core-win32-arm64-msvc": "1.3.100", - "@swc/core-win32-ia32-msvc": "1.3.100", - "@swc/core-win32-x64-msvc": "1.3.100" + "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" }, - "peerDependencies": { - "@swc/helpers": "^0.5.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" }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } + "engines": { + "node": ">=4.0" } }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.100.tgz", - "integrity": "sha512-XVWFsKe6ei+SsDbwmsuRkYck1SXRpO60Hioa4hoLwR8fxbA9eVp6enZtMxzVVMBi8ej5seZ4HZQeAWepbukiBw==", - "cpu": [ - "arm64" - ], + "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, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=10" + "node": ">=4.0" } }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.100.tgz", - "integrity": "sha512-KF/MXrnH1nakm1wbt4XV8FS7kvqD9TGmVxeJ0U4bbvxXMvzeYUurzg3AJUTXYmXDhH/VXOYJE5N5RkwZZPs5iA==", - "cpu": [ - "x64" - ], + "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, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.100.tgz", - "integrity": "sha512-p8hikNnAEJrw5vHCtKiFT4hdlQxk1V7vqPmvUDgL/qe2menQDK/i12tbz7/3BEQ4UqUPnvwpmVn2d19RdEMNxw==", - "cpu": [ - "arm64" - ], + "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-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, "engines": { - "node": ">=10" + "node": ">=8.6.0" } }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.100.tgz", - "integrity": "sha512-BWx/0EeY89WC4q3AaIaBSGfQxkYxIlS3mX19dwy2FWJs/O+fMvF9oLk/CyJPOZzbp+1DjGeeoGFuDYpiNO91JA==", - "cpu": [ - "arm64" - ], + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "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/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.100.tgz", - "integrity": "sha512-XUdGu3dxAkjsahLYnm8WijPfKebo+jHgHphDxaW0ovI6sTdmEGFDew7QzKZRlbYL2jRkUuuKuDGvD6lO5frmhA==", - "cpu": [ - "x64" - ], + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "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, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.100.tgz", - "integrity": "sha512-PhoXKf+f0OaNW/GCuXjJ0/KfK9EJX7z2gko+7nVnEA0p3aaPtbP6cq1Ubbl6CMoPL+Ci3gZ7nYumDqXNc3CtLQ==", - "cpu": [ - "x64" - ], + "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, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, "engines": { - "node": ">=10" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.100.tgz", - "integrity": "sha512-PwLADZN6F9cXn4Jw52FeP/MCLVHm8vwouZZSOoOScDtihjY495SSjdPnlosMaRSR4wJQssGwiD/4MbpgQPqbAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/focus-lock": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.3.tgz", + "integrity": "sha512-hfXkZha7Xt4RQtrL1HBfspAuIj89Y0fb6GX0dfJilb8S2G/lvL4akPAcHq6xoD2NuZnDMCnZL/zQesMyeu6Psg==", + "dependencies": { + "tslib": "^2.0.3" + }, "engines": { "node": ">=10" } }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.100.tgz", - "integrity": "sha512-0f6nicKSLlDKlyPRl2JEmkpBV4aeDfRQg6n8mPqgL7bliZIcDahG0ej+HxgNjZfS3e0yjDxsNRa6sAqWU2Z60A==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } ], "engines": { - "node": ">=10" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.100", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.100.tgz", - "integrity": "sha512-b7J0rPoMkRTa3XyUGt8PwCaIBuYWsL2DqbirrQKRESzgCvif5iNpqaM6kjIjI/5y5q1Ycv564CB51YDpiS8EtQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/@swc/counter": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", - "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", - "dev": true + "node_modules/framer-motion": { + "version": "10.16.16", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.16.tgz", + "integrity": "sha512-je6j91rd7NmUX7L1XHouwJ4v3R+SO4umso2LUcgOct3rHZ0PajZ80ETYZTajzEXEl9DlKyzjyt4AvGQ+lrebOw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } }, - "node_modules/@swc/types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", - "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true }, - "node_modules/@types/node": { - "version": "20.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", - "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", - "dev": true, + "node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", "dependencies": { - "undici-types": "~5.26.4" + "tslib": "2.4.0" } }, - "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "node_modules/framesync/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, - "node_modules/@types/react": { - "version": "18.2.39", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", - "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" } }, - "node_modules/@types/react-dom": { - "version": "18.2.17", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", - "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", + "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==" + }, + "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, - "dependencies": { - "@types/react": "*" + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", - "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", - "dev": true, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "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": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "node": "*" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/parser": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", - "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", + "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": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", - "debug": "^4.3.4" + "is-glob": "^4.0.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=10.13.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", - "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "type-fest": "^0.20.2" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", - "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/types": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", - "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "csstype": "^3.0.10" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", - "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "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/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" }, - "engines": { - "node": "^16.0.0 || >=18.0.0" + "bin": { + "handlebars": "bin/handlebars" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": ">=0.4.7" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/@typescript-eslint/utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", - "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "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, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "semver": "^7.5.4" - }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "node": ">=8" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", - "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", - "dev": true, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "eslint-visitor-keys": "^3.4.1" + "function-bind": "^1.1.2" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 0.4" } }, - "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/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } }, - "node_modules/@vitejs/plugin-react-swc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", - "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==", + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "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==", "dependencies": { - "@swc/core": "^1.3.96" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, - "peerDependencies": { - "vite": "^4 || ^5" + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" + "node": ">=0.8.19" } }, - "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/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "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, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "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" + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": ">=8" + "node": ">=0.10.0" } }, - "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==", + "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": { - "color-convert": "^2.0.1" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=0.10.0" } }, - "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/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "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/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "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/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "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": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "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==", + "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/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@apidevtools/json-schema-ref-parser": "9.0.9" + }, + "engines": { + "node": ">=10" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "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/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "universalify": "^2.0.0" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "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/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "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": ">=6" + "node": ">= 0.8.0" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "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" }, @@ -1286,183 +4103,236 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "yallist": "^4.0.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=8.6" } }, - "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/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" }, - "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==", + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "delayed-stream": "~1.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "engines": { - "node": ">=16" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "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/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, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "big-integer": "^1.6.16" } }, - "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, - "dependencies": { - "ms": "2.1.2" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "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==", + "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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "engines": { - "node": ">=0.4.0" + "node": ">=0.10.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, + "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==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-typescript-codegen": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.25.0.tgz", + "integrity": "sha512-nN/TnIcGbP58qYgwEEy5FrAAjePcYgfMaCe3tsmYyTgI3v4RR9v8os14L+LEWDvV50+CmqiyTzRkKKtJeb6Ybg==", "dev": true, "dependencies": { - "path-type": "^4.0.0" + "camelcase": "^6.3.0", + "commander": "^11.0.0", + "fs-extra": "^11.1.1", + "handlebars": "^4.7.7", + "json-schema-ref-parser": "^9.0.9" }, - "engines": { - "node": ">=8" + "bin": { + "openapi": "bin/index.js" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "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": { - "esutils": "^2.0.2" + "@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": ">=6.0.0" + "node": ">= 0.8.0" } }, - "node_modules/esbuild": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", - "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "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, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=12" + "node": ">=10" }, - "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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" }, @@ -1470,390 +4340,566 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", - "dev": true, + "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==", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", - "@humanwhocodes/config-array": "^0.11.13", - "@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" + "callsites": "^3.0.0" }, - "bin": { - "eslint": "bin/eslint.js" + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, - "engines": { - "node": ">=10" + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.4.tgz", - "integrity": "sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==", + "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, - "peerDependencies": { - "eslint": ">=7" + "engines": { + "node": ">= 0.8.0" } }, - "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==", + "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, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "bin": { + "prettier": "bin/prettier.cjs" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=14" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "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==", + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/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": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6" } }, - "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==", + "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": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "loose-envify": "^1.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=0.10.0" + } + }, + "node_modules/react-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "dependencies": { + "@babel/runtime": "^7.12.13" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, - "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, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dependencies": { - "estraverse": "^5.1.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" }, - "engines": { - "node": ">=0.10" + "peerDependencies": { + "react": "^18.2.0" } }, - "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, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-focus-lock": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.11.1.tgz", + "integrity": "sha512-IXLwnTBrLTlKTpASZXqqXJ8oymWrgAlOfuuDYN4XCuN1YJ72dwX198UCaF1QqGUk5C3QOnlMik//n3ufcfe8Ig==", "dependencies": { - "estraverse": "^5.2.0" + "@babel/runtime": "^7.0.0", + "focus-lock": "^1.3.2", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" }, - "engines": { - "node": ">=4.0" + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "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, + "node_modules/react-hook-form": { + "version": "7.49.3", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", + "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==", "engines": { - "node": ">=4.0" + "node": ">=18", + "pnpm": "8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" } }, - "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/react-icons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", + "integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==", + "peerDependencies": { + "react": "*" } }, - "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/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" }, - "engines": { - "node": ">=8.6.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", "dependencies": { - "is-glob": "^4.0.1" + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": 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.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.5.tgz", + "integrity": "sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==", "dependencies": { - "reusify": "^1.0.4" + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "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, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", "dependencies": { - "flat-cache": "^3.0.4" + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { - "to-regex-range": "^5.0.1" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", "engines": { - "node": ">=8" + "node": ">=4" } }, - "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==", + "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==", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">=10" + "bin": { + "rimraf": "bin.js" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "node_modules/rollup": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", + "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.6.1", + "@rollup/rollup-android-arm64": "4.6.1", + "@rollup/rollup-darwin-arm64": "4.6.1", + "@rollup/rollup-darwin-x64": "4.6.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.6.1", + "@rollup/rollup-linux-arm64-gnu": "4.6.1", + "@rollup/rollup-linux-arm64-musl": "4.6.1", + "@rollup/rollup-linux-x64-gnu": "4.6.1", + "@rollup/rollup-linux-x64-musl": "4.6.1", + "@rollup/rollup-win32-arm64-msvc": "4.6.1", + "@rollup/rollup-win32-ia32-msvc": "4.6.1", + "@rollup/rollup-win32-x64-msvc": "4.6.1", + "fsevents": "~2.3.2" } }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "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": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "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==", + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">= 6" + "node": ">=10" } }, - "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "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": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=14.14" + "node": ">=8" } }, - "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/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/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=8" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "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": ">=0.10.0" } }, - "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==", + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, "engines": { - "node": ">=10.13.0" + "node": ">=0.10.0" } }, - "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "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": { - "type-fest": "^0.20.2" + "ansi-regex": "^5.0.1" }, + "engines": { + "node": ">=8" + } + }, + "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" }, @@ -1861,1035 +4907,1559 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "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": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "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/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "is-number": "^7.0.0" }, "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "node": ">=8.0" } }, - "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==", + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" } }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "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": ">= 4" + "node": ">= 0.8.0" } }, - "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==", + "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, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, "engines": { - "node": ">=6" + "node": ">=10" }, "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==", + "node_modules/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, "engines": { - "node": ">=0.8.19" + "node": ">=14.17" } }, - "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==", + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "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==", + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 10.0.0" } }, - "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==", + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, + "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": { - "is-extglob": "^2.1.1" + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", + "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", + "dependencies": { + "tslib": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "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==", + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/vite": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.4.tgz", + "integrity": "sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==", "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.31", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=8" + "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/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "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==", + "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": { - "argparse": "^2.0.1" + "isexe": "^2.0.0" }, "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "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-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", - "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", - "dev": true, - "dependencies": { - "@apidevtools/json-schema-ref-parser": "9.0.9" + "node-which": "bin/node-which" }, "engines": { - "node": ">=10" + "node": ">= 8" } }, - "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==", + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "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==", + "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==" + }, + "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/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "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, - "dependencies": { - "universalify": "^2.0.0" + "engines": { + "node": ">=10" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "dev": true, - "dependencies": { - "json-buffer": "3.0.1" + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, - "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, + "node_modules/zustand": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.0.tgz", + "integrity": "sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "use-sync-external-store": "1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } } + } + }, + "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true }, - "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==", + "@apidevtools/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "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 + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "requires": { + "@babel/types": "^7.22.15" + } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==" + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" }, - "bin": { - "loose-envify": "cli.js" + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "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" + "@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "requires": { + "regenerator-runtime": "^0.14.0" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" + "@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" } }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" + "@chakra-ui/accordion": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-2.3.1.tgz", + "integrity": "sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==", + "requires": { + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" + } + }, + "@chakra-ui/alert": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-2.2.2.tgz", + "integrity": "sha512-jHg4LYMRNOJH830ViLuicjb3F+v6iriE/2G5T+Sd0Hna04nukNJ1MxUmBPE+vI22me2dIflfelu2v9wdB6Pojw==", + "requires": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" + "@chakra-ui/anatomy": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", + "integrity": "sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==" + }, + "@chakra-ui/avatar": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-2.3.0.tgz", + "integrity": "sha512-8gKSyLfygnaotbJbDMHDiJoF38OHXUYVme4gGxZ1fLnQEdPVEaIWfH+NndIjOM0z8S+YEFnT9KyGMUtvPrBk3g==", + "requires": { + "@chakra-ui/image": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "@chakra-ui/breadcrumb": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-2.2.0.tgz", + "integrity": "sha512-4cWCG24flYBxjruRi4RJREWTGF74L/KzI2CognAW/d/zWR0CjiScuJhf37Am3LFbCySP6WSoyBOtTIoTA4yLEA==", + "requires": { + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "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": "*" + "@chakra-ui/breakpoint-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.8.tgz", + "integrity": "sha512-Pq32MlEX9fwb5j5xx8s18zJMARNHlQZH2VH1RZgfgRDpp7DcEgtRW5AInfN5CfqdHLO1dGxA7I3MqEuL5JnIsA==", + "requires": { + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/button": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-2.1.0.tgz", + "integrity": "sha512-95CplwlRKmmUXkdEp/21VkEWgnwcx2TOBG6NfYlsuLBDHSLlo5FKIiE2oSi4zXc4TLcopGcWPNcm/NDaSC5pvA==", + "requires": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@chakra-ui/card": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/card/-/card-2.2.0.tgz", + "integrity": "sha512-xUB/k5MURj4CtPAhdSoXZidUbm8j3hci9vnc+eZJVDqhDOShNlD6QeniQNRPRys4lWAQLCbFcrwL29C8naDi6g==", + "requires": { + "@chakra-ui/shared-utils": "2.0.5" } }, - "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 + "@chakra-ui/checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-2.3.2.tgz", + "integrity": "sha512-85g38JIXMEv6M+AcyIGLh7igNtfpAN6KGQFYxY9tBj0eWvWk4NKQxvqqyVta0bSAyIl1rixNIIezNpNWk2iO4g==", + "requires": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/visually-hidden": "2.2.0", + "@zag-js/focus-visible": "0.16.0" + } + }, + "@chakra-ui/clickable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.1.0.tgz", + "integrity": "sha512-flRA/ClPUGPYabu+/GLREZVZr9j2uyyazCAUHAdrTUEdDYCr31SVGhgh7dgKdtq23bOvAQJpIJjw/0Bs0WvbXw==", + "requires": { + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "@chakra-ui/close-button": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-2.1.1.tgz", + "integrity": "sha512-gnpENKOanKexswSVpVz7ojZEALl2x5qjLYNqSQGbxz+aP9sOXPfUS56ebyBrre7T7exuWGiFeRwnM0oVeGPaiw==", + "requires": { + "@chakra-ui/icon": "3.2.0" } }, - "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 + "@chakra-ui/color-mode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-2.2.0.tgz", + "integrity": "sha512-niTEA8PALtMWRI9wJ4LL0CSBDo8NBfLNp4GD6/0hstcm3IlbBHTVKxN6HwSaoNYfphDQLxCjT4yG+0BJA5tFpg==", + "requires": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "@chakra-ui/control-box": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-2.1.0.tgz", + "integrity": "sha512-gVrRDyXFdMd8E7rulL0SKeoljkLQiPITFnsyMO8EFHNZ+AHt5wK4LIguYVEq88APqAGZGfHFWXr79RYrNiE3Mg==", + "requires": {} }, - "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" + "@chakra-ui/counter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.1.0.tgz", + "integrity": "sha512-s6hZAEcWT5zzjNz2JIWUBzRubo9la/oof1W7EKZVVfPYHERnl5e16FmBC79Yfq8p09LQ+aqFKm/etYoJMMgghw==", + "requires": { + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "node_modules/openapi-typescript-codegen": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.25.0.tgz", - "integrity": "sha512-nN/TnIcGbP58qYgwEEy5FrAAjePcYgfMaCe3tsmYyTgI3v4RR9v8os14L+LEWDvV50+CmqiyTzRkKKtJeb6Ybg==", - "dev": true, - "dependencies": { - "camelcase": "^6.3.0", - "commander": "^11.0.0", - "fs-extra": "^11.1.1", - "handlebars": "^4.7.7", - "json-schema-ref-parser": "^9.0.9" - }, - "bin": { - "openapi": "bin/index.js" - } + "@chakra-ui/css-reset": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-2.3.0.tgz", + "integrity": "sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==", + "requires": {} }, - "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" + "@chakra-ui/descendant": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-3.1.0.tgz", + "integrity": "sha512-VxCIAir08g5w27klLyi7PVo8BxhW4tgU/lxQyujkmi4zx7hT9ZdrcQLAted/dAa+aSIZ14S1oV0Q9lGjsAdxUQ==", + "requires": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0" } }, - "node_modules/p-limit": { + "@chakra-ui/dom-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/dom-utils/-/dom-utils-2.1.0.tgz", + "integrity": "sha512-ZmF2qRa1QZ0CMLU8M1zCfmw29DmPNtfjR9iTo74U5FPr3i1aoAh7fbJ4qAlZ197Xw9eAW28tvzQuoVWeL5C7fQ==" + }, + "@chakra-ui/editable": { "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" + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-3.1.0.tgz", + "integrity": "sha512-j2JLrUL9wgg4YA6jLlbU88370eCRyor7DZQD9lzpY95tSOXpTljeg3uF9eOmDnCs6fxp3zDWIfkgMm/ExhcGTg==", + "requires": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/event-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/event-utils/-/event-utils-2.0.8.tgz", + "integrity": "sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==" + }, + "@chakra-ui/focus-lock": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-2.1.0.tgz", + "integrity": "sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==", + "requires": { + "@chakra-ui/dom-utils": "2.1.0", + "react-focus-lock": "^2.9.4" } }, - "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" + "@chakra-ui/form-control": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-2.2.0.tgz", + "integrity": "sha512-wehLC1t4fafCVJ2RvJQT2jyqsAwX7KymmiGqBu7nQoQz8ApTkGABWpo/QwDh3F/dBLrouHDoOvGmYTqft3Mirw==", + "requires": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "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" + "@chakra-ui/hooks": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-2.2.1.tgz", + "integrity": "sha512-RQbTnzl6b1tBjbDPf9zGRo9rf/pQMholsOudTxjy4i9GfTfz6kgp5ValGjQm2z7ng6Z31N1cnjZ1AlSzQ//ZfQ==", + "requires": { + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/utils": "2.0.15", + "compute-scroll-into-view": "3.0.3", + "copy-to-clipboard": "3.3.3" } }, - "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" + "@chakra-ui/icon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.2.0.tgz", + "integrity": "sha512-xxjGLvlX2Ys4H0iHrI16t74rG9EBcpFvJ3Y3B7KMQTrnW34Kf7Da/UC8J67Gtx85mTHW020ml85SVPKORWNNKQ==", + "requires": { + "@chakra-ui/shared-utils": "2.0.5" } }, - "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" + "@chakra-ui/icons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", + "requires": { + "@chakra-ui/icon": "3.2.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" + "@chakra-ui/image": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", + "integrity": "sha512-bskumBYKLiLMySIWDGcz0+D9Th0jPvmX6xnRMs4o92tT3Od/bW26lahmV2a2Op2ItXeCmRMY+XxJH5Gy1i46VA==", + "requires": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" + "@chakra-ui/input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-2.1.2.tgz", + "integrity": "sha512-GiBbb3EqAA8Ph43yGa6Mc+kUPjh4Spmxp1Pkelr8qtudpc3p2PJOOebLpd90mcqw8UePPa+l6YhhPtp6o0irhw==", + "requires": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "@chakra-ui/layout": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-2.3.1.tgz", + "integrity": "sha512-nXuZ6WRbq0WdgnRgLw+QuxWAHuhDtVX8ElWqcTK+cSMFg/52eVP47czYBE5F35YhnoW2XBwfNoNgZ7+e8Z01Rg==", + "requires": { + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } }, - "node_modules/picomatch": { + "@chakra-ui/lazy-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/lazy-utils/-/lazy-utils-2.0.5.tgz", + "integrity": "sha512-UULqw7FBvcckQk2n3iPO56TMJvDsNv0FKZI6PlUNJVaGsPbsYxK/8IQ60vZgaTVPtVcjY6BE+y6zg8u9HOqpyg==" + }, + "@chakra-ui/live-region": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-2.1.0.tgz", + "integrity": "sha512-ZOxFXwtaLIsXjqnszYYrVuswBhnIHHP+XIgK1vC6DePKtyK590Wg+0J0slDwThUAd4MSSIUa/nNX84x1GMphWw==", + "requires": {} + }, + "@chakra-ui/media-query": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-3.3.0.tgz", + "integrity": "sha512-IsTGgFLoICVoPRp9ykOgqmdMotJG0CnPsKvGQeSFOB/dZfIujdVb14TYxDU4+MURXry1MhJ7LzZhv+Ml7cr8/g==", + "requires": { + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/menu": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-2.2.1.tgz", + "integrity": "sha512-lJS7XEObzJxsOwWQh7yfG4H8FzFPRP5hVPN/CL+JzytEINCSBvsCDHrYPQGp7jzpCi8vnTqQQGQe0f8dwnXd2g==", + "requires": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-outside-click": "2.2.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" + } + }, + "@chakra-ui/modal": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-2.3.1.tgz", + "integrity": "sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==", + "requires": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0", + "aria-hidden": "^1.2.3", + "react-remove-scroll": "^2.5.6" + } + }, + "@chakra-ui/number-input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-2.1.2.tgz", + "integrity": "sha512-pfOdX02sqUN0qC2ysuvgVDiws7xZ20XDIlcNhva55Jgm095xjm8eVdIBfNm3SFbSUNxyXvLTW/YQanX74tKmuA==", + "requires": { + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-interval": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/number-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-utils/-/number-utils-2.0.7.tgz", + "integrity": "sha512-yOGxBjXNvLTBvQyhMDqGU0Oj26s91mbAlqKHiuw737AXHt0aPllOthVUqQMeaYLwLCjGMg0jtI7JReRzyi94Dg==" + }, + "@chakra-ui/object-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/object-utils/-/object-utils-2.1.0.tgz", + "integrity": "sha512-tgIZOgLHaoti5PYGPTwK3t/cqtcycW0owaiOXoZOcpwwX/vlVb+H1jFsQyWiiwQVPt9RkoSLtxzXamx+aHH+bQ==" + }, + "@chakra-ui/pin-input": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-2.1.0.tgz", + "integrity": "sha512-x4vBqLStDxJFMt+jdAHHS8jbh294O53CPQJoL4g228P513rHylV/uPscYUHrVJXRxsHfRztQO9k45jjTYaPRMw==", + "requires": { + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/popover": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-2.2.1.tgz", + "integrity": "sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==", + "requires": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/popper": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-3.1.0.tgz", + "integrity": "sha512-ciDdpdYbeFG7og6/6J8lkTFxsSvwTdMLFkpVylAF6VNC22jssiWfquj2eyD4rJnzkRFPvIWJq8hvbfhsm+AjSg==", + "requires": { + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@popperjs/core": "^2.9.3" } }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, - "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.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" + "@chakra-ui/portal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-2.1.0.tgz", + "integrity": "sha512-9q9KWf6SArEcIq1gGofNcFPSWEyl+MfJjEUg/un1SMlQjaROOh3zYr+6JAwvcORiX7tyHosnmWC3d3wI2aPSQg==", + "requires": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" } }, - "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" + "@chakra-ui/progress": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.2.0.tgz", + "integrity": "sha512-qUXuKbuhN60EzDD9mHR7B67D7p/ZqNS2Aze4Pbl1qGGZfulPW0PY8Rof32qDtttDQBkzQIzFGE8d9QpAemToIQ==", + "requires": { + "@chakra-ui/react-context": "2.1.0" + } + }, + "@chakra-ui/provider": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-2.4.2.tgz", + "integrity": "sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==", + "requires": { + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/utils": "2.0.15" + } + }, + "@chakra-ui/radio": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-2.1.2.tgz", + "integrity": "sha512-n10M46wJrMGbonaghvSRnZ9ToTv/q76Szz284gv4QUWvyljQACcGrXIONUnQ3BIwbOfkRqSk7Xl/JgZtVfll+w==", + "requires": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@zag-js/focus-visible": "0.16.0" + } + }, + "@chakra-ui/react": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.8.2.tgz", + "integrity": "sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==", + "requires": { + "@chakra-ui/accordion": "2.3.1", + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/avatar": "2.3.0", + "@chakra-ui/breadcrumb": "2.2.0", + "@chakra-ui/button": "2.1.0", + "@chakra-ui/card": "2.2.0", + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/control-box": "2.1.0", + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/editable": "3.1.0", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/hooks": "2.2.1", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/image": "2.1.0", + "@chakra-ui/input": "2.1.2", + "@chakra-ui/layout": "2.3.1", + "@chakra-ui/live-region": "2.1.0", + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/menu": "2.2.1", + "@chakra-ui/modal": "2.3.1", + "@chakra-ui/number-input": "2.1.2", + "@chakra-ui/pin-input": "2.1.0", + "@chakra-ui/popover": "2.2.1", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/progress": "2.2.0", + "@chakra-ui/provider": "2.4.2", + "@chakra-ui/radio": "2.1.2", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/select": "2.1.2", + "@chakra-ui/skeleton": "2.1.0", + "@chakra-ui/skip-nav": "2.1.0", + "@chakra-ui/slider": "2.1.0", + "@chakra-ui/spinner": "2.1.0", + "@chakra-ui/stat": "2.1.1", + "@chakra-ui/stepper": "2.3.1", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/switch": "2.1.2", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/table": "2.1.0", + "@chakra-ui/tabs": "3.0.0", + "@chakra-ui/tag": "3.1.1", + "@chakra-ui/textarea": "2.1.2", + "@chakra-ui/theme": "3.3.1", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/toast": "7.0.2", + "@chakra-ui/tooltip": "2.3.1", + "@chakra-ui/transition": "2.1.0", + "@chakra-ui/utils": "2.0.15", + "@chakra-ui/visually-hidden": "2.2.0" + } + }, + "@chakra-ui/react-children-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-children-utils/-/react-children-utils-2.0.6.tgz", + "integrity": "sha512-QVR2RC7QsOsbWwEnq9YduhpqSFnZGvjjGREV8ygKi8ADhXh93C8azLECCUVgRJF2Wc+So1fgxmjLcbZfY2VmBA==", + "requires": {} + }, + "@chakra-ui/react-context": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-context/-/react-context-2.1.0.tgz", + "integrity": "sha512-iahyStvzQ4AOwKwdPReLGfDesGG+vWJfEsn0X/NoGph/SkN+HXtv2sCfYFFR9k7bb+Kvc6YfpLlSuLvKMHi2+w==", + "requires": {} + }, + "@chakra-ui/react-env": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-3.1.0.tgz", + "integrity": "sha512-Vr96GV2LNBth3+IKzr/rq1IcnkXv+MLmwjQH6C8BRtn3sNskgDFD5vLkVXcEhagzZMCh8FR3V/bzZPojBOyNhw==", + "requires": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "@chakra-ui/react-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-types/-/react-types-2.0.7.tgz", + "integrity": "sha512-12zv2qIZ8EHwiytggtGvo4iLT0APris7T0qaAWqzpUGS0cdUtR8W+V1BJ5Ocq+7tA6dzQ/7+w5hmXih61TuhWQ==", + "requires": {} }, - "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" + "@chakra-ui/react-use-animation-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.1.0.tgz", + "integrity": "sha512-CFZkQU3gmDBwhqy0vC1ryf90BVHxVN8cTLpSyCpdmExUEtSEInSCGMydj2fvn7QXsz/za8JNdO2xxgJwxpLMtg==", + "requires": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0" } }, - "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" - } - ] + "@chakra-ui/react-use-callback-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.1.0.tgz", + "integrity": "sha512-efnJrBtGDa4YaxDzDE90EnKD3Vkh5a1t3w7PhnRQmsphLy3g2UieasoKTlT2Hn118TwDjIv5ZjHJW6HbzXA9wQ==", + "requires": {} }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" + "@chakra-ui/react-use-controllable-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.1.0.tgz", + "integrity": "sha512-QR/8fKNokxZUs4PfxjXuwl0fj/d71WPrmLJvEpCTkHjnzu7LnYvzoe2wB867IdooQJL0G1zBxl0Dq+6W1P3jpg==", + "requires": { + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" + "@chakra-ui/react-use-disclosure": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.1.0.tgz", + "integrity": "sha512-Ax4pmxA9LBGMyEZJhhUZobg9C0t3qFE4jVF1tGBsrLDcdBeLR9fwOogIPY9Hf0/wqSlAryAimICbr5hkpa5GSw==", + "requires": { + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "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" + "@chakra-ui/react-use-event-listener": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.1.0.tgz", + "integrity": "sha512-U5greryDLS8ISP69DKDsYcsXRtAdnTQT+jjIlRYZ49K/XhUR/AqVZCK5BkR1spTDmO9H8SPhgeNKI70ODuDU/Q==", + "requires": { + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "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" + "@chakra-ui/react-use-focus-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.1.0.tgz", + "integrity": "sha512-xzVboNy7J64xveLcxTIJ3jv+lUJKDwRM7Szwn9tNzUIPD94O3qwjV7DDCUzN2490nSYDF4OBMt/wuDBtaR3kUQ==", + "requires": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.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" + "@chakra-ui/react-use-focus-on-pointer-down": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.1.0.tgz", + "integrity": "sha512-2jzrUZ+aiCG/cfanrolsnSMDykCAbv9EK/4iUyZno6BYb3vziucmvgKuoXbMPAzWNtwUwtuMhkby8rc61Ue+Lg==", + "requires": { + "@chakra-ui/react-use-event-listener": "2.1.0" } }, - "node_modules/rollup": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", - "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.6.1", - "@rollup/rollup-android-arm64": "4.6.1", - "@rollup/rollup-darwin-arm64": "4.6.1", - "@rollup/rollup-darwin-x64": "4.6.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.6.1", - "@rollup/rollup-linux-arm64-gnu": "4.6.1", - "@rollup/rollup-linux-arm64-musl": "4.6.1", - "@rollup/rollup-linux-x64-gnu": "4.6.1", - "@rollup/rollup-linux-x64-musl": "4.6.1", - "@rollup/rollup-win32-arm64-msvc": "4.6.1", - "@rollup/rollup-win32-ia32-msvc": "4.6.1", - "@rollup/rollup-win32-x64-msvc": "4.6.1", - "fsevents": "~2.3.2" + "@chakra-ui/react-use-interval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-interval/-/react-use-interval-2.1.0.tgz", + "integrity": "sha512-8iWj+I/+A0J08pgEXP1J1flcvhLBHkk0ln7ZvGIyXiEyM6XagOTJpwNhiu+Bmk59t3HoV/VyvyJTa+44sEApuw==", + "requires": { + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "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" - } + "@chakra-ui/react-use-latest-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-latest-ref/-/react-use-latest-ref-2.1.0.tgz", + "integrity": "sha512-m0kxuIYqoYB0va9Z2aW4xP/5b7BzlDeWwyXCH6QpT2PpW3/281L3hLCm1G0eOUcdVlayqrQqOeD6Mglq+5/xoQ==", + "requires": {} }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" + "@chakra-ui/react-use-merge-refs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.1.0.tgz", + "integrity": "sha512-lERa6AWF1cjEtWSGjxWTaSMvneccnAVH4V4ozh8SYiN9fSPZLlSG3kNxfNzdFvMEhM7dnP60vynF7WjGdTgQbQ==", + "requires": {} + }, + "@chakra-ui/react-use-outside-click": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.2.0.tgz", + "integrity": "sha512-PNX+s/JEaMneijbgAM4iFL+f3m1ga9+6QK0E5Yh4s8KZJQ/bLwZzdhMz8J/+mL+XEXQ5J0N8ivZN28B82N1kNw==", + "requires": { + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "@chakra-ui/react-use-pan-event": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.1.0.tgz", + "integrity": "sha512-xmL2qOHiXqfcj0q7ZK5s9UjTh4Gz0/gL9jcWPA6GVf+A0Od5imEDa/Vz+533yQKWiNSm1QGrIj0eJAokc7O4fg==", + "requires": { + "@chakra-ui/event-utils": "2.0.8", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "framesync": "6.1.2" } }, - "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" + "@chakra-ui/react-use-previous": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-previous/-/react-use-previous-2.1.0.tgz", + "integrity": "sha512-pjxGwue1hX8AFcmjZ2XfrQtIJgqbTF3Qs1Dy3d1krC77dEsiCUbQ9GzOBfDc8pfd60DrB5N2tg5JyHbypqh0Sg==", + "requires": {} + }, + "@chakra-ui/react-use-safe-layout-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.1.0.tgz", + "integrity": "sha512-Knbrrx/bcPwVS1TorFdzrK/zWA8yuU/eaXDkNj24IrKoRlQrSBFarcgAEzlCHtzuhufP3OULPkELTzz91b0tCw==", + "requires": {} + }, + "@chakra-ui/react-use-size": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-size/-/react-use-size-2.1.0.tgz", + "integrity": "sha512-tbLqrQhbnqOjzTaMlYytp7wY8BW1JpL78iG7Ru1DlV4EWGiAmXFGvtnEt9HftU0NJ0aJyjgymkxfVGI55/1Z4A==", + "requires": { + "@zag-js/element-size": "0.10.5" } }, - "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" + "@chakra-ui/react-use-timeout": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-timeout/-/react-use-timeout-2.1.0.tgz", + "integrity": "sha512-cFN0sobKMM9hXUhyCofx3/Mjlzah6ADaEl/AXl5Y+GawB5rgedgAcu2ErAgarEkwvsKdP6c68CKjQ9dmTQlJxQ==", + "requires": { + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" + "@chakra-ui/react-use-update-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.1.0.tgz", + "integrity": "sha512-ND4Q23tETaR2Qd3zwCKYOOS1dfssojPLJMLvUtUbW5M9uW1ejYWgGUobeAiOVfSplownG8QYMmHTP86p/v0lbA==", + "requires": {} + }, + "@chakra-ui/react-utils": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-2.0.12.tgz", + "integrity": "sha512-GbSfVb283+YA3kA8w8xWmzbjNWk14uhNpntnipHCftBibl0lxtQ9YqMFQLwuFOO0U2gYVocszqqDWX+XNKq9hw==", + "requires": { + "@chakra-ui/utils": "2.0.15" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "@chakra-ui/select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.1.2.tgz", + "integrity": "sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==", + "requires": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "@chakra-ui/shared-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz", + "integrity": "sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==" + }, + "@chakra-ui/skeleton": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-2.1.0.tgz", + "integrity": "sha512-JNRuMPpdZGd6zFVKjVQ0iusu3tXAdI29n4ZENYwAJEMf/fN0l12sVeirOxkJ7oEL0yOx2AgEYFSKdbcAgfUsAQ==", + "requires": { + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/react-use-previous": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "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" + "@chakra-ui/skip-nav": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skip-nav/-/skip-nav-2.1.0.tgz", + "integrity": "sha512-Hk+FG+vadBSH0/7hwp9LJnLjkO0RPGnx7gBJWI4/SpoJf3e4tZlWYtwGj0toYY4aGKl93jVghuwGbDBEMoHDug==", + "requires": {} + }, + "@chakra-ui/slider": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-2.1.0.tgz", + "integrity": "sha512-lUOBcLMCnFZiA/s2NONXhELJh6sY5WtbRykPtclGfynqqOo47lwWJx+VP7xaeuhDOPcWSSecWc9Y1BfPOCz9cQ==", + "requires": { + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-pan-event": "2.1.0", + "@chakra-ui/react-use-size": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" + } + }, + "@chakra-ui/spinner": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-2.1.0.tgz", + "integrity": "sha512-hczbnoXt+MMv/d3gE+hjQhmkzLiKuoTo42YhUG7Bs9OSv2lg1fZHW1fGNRFP3wTi6OIbD044U1P9HK+AOgFH3g==", + "requires": { + "@chakra-ui/shared-utils": "2.0.5" } }, - "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" + "@chakra-ui/stat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-2.1.1.tgz", + "integrity": "sha512-LDn0d/LXQNbAn2KaR3F1zivsZCewY4Jsy1qShmfBMKwn6rI8yVlbvu6SiA3OpHS0FhxbsZxQI6HefEoIgtqY6Q==", + "requires": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "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" + "@chakra-ui/stepper": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stepper/-/stepper-2.3.1.tgz", + "integrity": "sha512-ky77lZbW60zYkSXhYz7kbItUpAQfEdycT0Q4bkHLxfqbuiGMf8OmgZOQkOB9uM4v0zPwy2HXhe0vq4Dd0xa55Q==", + "requires": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "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 + "@chakra-ui/styled-system": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.9.2.tgz", + "integrity": "sha512-To/Z92oHpIE+4nk11uVMWqo2GGRS86coeMmjxtpnErmWRdLcp1WVCVRAvn+ZwpLiNR+reWFr2FFqJRsREuZdAg==", + "requires": { + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.1.2", + "lodash.mergewith": "4.6.2" + } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "@chakra-ui/switch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-2.1.2.tgz", + "integrity": "sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==", + "requires": { + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/shared-utils": "2.0.5" } }, - "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "dev": true, - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" + "@chakra-ui/system": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-2.6.2.tgz", + "integrity": "sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==", + "requires": { + "@chakra-ui/color-mode": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/utils": "2.0.15", + "react-fast-compare": "3.2.2" } }, - "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" + "@chakra-ui/table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-2.1.0.tgz", + "integrity": "sha512-o5OrjoHCh5uCLdiUb0Oc0vq9rIAeHSIRScc2ExTC9Qg/uVZl2ygLrjToCaKfaaKl1oQexIeAcZDKvPG8tVkHyQ==", + "requires": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "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" + "@chakra-ui/tabs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-3.0.0.tgz", + "integrity": "sha512-6Mlclp8L9lqXmsGWF5q5gmemZXOiOYuh0SGT/7PgJVNPz3LXREXlXg2an4MBUD8W5oTkduCX+3KTMCwRrVrDYw==", + "requires": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/tag": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-3.1.1.tgz", + "integrity": "sha512-Bdel79Dv86Hnge2PKOU+t8H28nm/7Y3cKd4Kfk9k3lOpUh4+nkSGe58dhRzht59lEqa4N9waCgQiBdkydjvBXQ==", + "requires": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0" } }, - "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" + "@chakra-ui/textarea": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-2.1.2.tgz", + "integrity": "sha512-ip7tvklVCZUb2fOHDb23qPy/Fr2mzDOGdkrpbNi50hDCiV4hFX02jdQJdi3ydHZUyVgZVBKPOJ+lT9i7sKA2wA==", + "requires": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" + "@chakra-ui/theme": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-3.3.1.tgz", + "integrity": "sha512-Hft/VaT8GYnItGCBbgWd75ICrIrIFrR7lVOhV/dQnqtfGqsVDlrztbSErvMkoPKt0UgAkd9/o44jmZ6X4U2nZQ==", + "requires": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.1.2" } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "@chakra-ui/theme-tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-2.1.2.tgz", + "integrity": "sha512-Qdj8ajF9kxY4gLrq7gA+Azp8CtFHGO9tWMN2wfF9aQNgG9AuMhPrUzMq9AMQ0MXiYcgNq/FD3eegB43nHVmXVA==", + "requires": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.2" + } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" + "@chakra-ui/theme-utils": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-utils/-/theme-utils-2.0.21.tgz", + "integrity": "sha512-FjH5LJbT794r0+VSCXB3lT4aubI24bLLRWB+CuRKHijRvsOg717bRdUN/N1fEmEpFnRVrbewttWh/OQs0EWpWw==", + "requires": { + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1", + "lodash.mergewith": "4.6.2" } }, - "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" + "@chakra-ui/toast": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-7.0.2.tgz", + "integrity": "sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==", + "requires": { + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-timeout": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1" + } + }, + "@chakra-ui/tooltip": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-2.3.1.tgz", + "integrity": "sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==", + "requires": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "node_modules/vite": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.4.tgz", - "integrity": "sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==", - "dev": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.31", - "rollup": "^4.2.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" + "@chakra-ui/transition": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-2.1.0.tgz", + "integrity": "sha512-orkT6T/Dt+/+kVwJNy7zwJ+U2xAZ3EU7M3XCs45RBvUnZDr/u9vdmaM/3D/rOpmQJWgQBwKPJleUXrYWUagEDQ==", + "requires": { + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "requires": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "@chakra-ui/visually-hidden": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-2.2.0.tgz", + "integrity": "sha512-KmKDg01SrQ7VbTD3+cPWf/UfpF5MSwm3v7MWi0n5t8HnnadT13MF0MJCDSXbBWnzLv1ZKJ6zlyAOeARWX+DpjQ==", + "requires": {} + }, + "@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" } } }, - "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" + "@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "requires": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, - "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 + "@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "requires": { + "@emotion/memoize": "^0.8.1" + } }, - "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 + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, - "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" + "@emotion/react": { + "version": "11.11.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", + "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.3.tgz", + "integrity": "sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==", + "requires": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true }, - "@apidevtools/json-schema-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", - "dev": true, + "@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", "requires": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.6", - "call-me-maybe": "^1.0.1", - "js-yaml": "^4.1.0" + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" } }, + "@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "@esbuild/android-arm": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", @@ -3074,6 +6644,26 @@ "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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 + } } }, "@eslint/js": { @@ -3137,6 +6727,11 @@ "fastq": "^1.6.0" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, "@rollup/rollup-android-arm-eabi": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", @@ -3315,12 +6910,86 @@ "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", "dev": true }, + "@tanstack/history": { + "version": "1.15.13", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.15.13.tgz", + "integrity": "sha512-ToaeMtK5S4YaxCywAlYexc7KPFN0esjyTZ4vXzJhXEWAkro9iHgh7m/4ozPJb7oTo65WkHWX0W9GjcZbInSD8w==" + }, + "@tanstack/react-router": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.19.1.tgz", + "integrity": "sha512-a4Xf074qo2fQLmSi8PTncEFn8XakaH3+DT7Dted4OPClzQFS+c6yU3HONVNAsuYWZ7lDK1HMKoHPDFbnHPEWvA==", + "requires": { + "@tanstack/history": "1.15.13", + "@tanstack/react-store": "^0.2.1", + "tiny-invariant": "^1.3.1", + "tiny-warning": "^1.0.3" + } + }, + "@tanstack/react-store": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.2.1.tgz", + "integrity": "sha512-tEbMCQjbeVw9KOP/202LfqZMSNAVi6zYkkp1kBom8nFuMx/965Hzes3+6G6b/comCwVxoJU8Gg9IrcF8yRPthw==", + "requires": { + "@tanstack/store": "0.1.3", + "use-sync-external-store": "^1.2.0" + } + }, + "@tanstack/router-devtools": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.19.1.tgz", + "integrity": "sha512-l560JHnffcDccSTo/sOtB+gKvtgaWYpOKOu9MyvswN9XB2pt752UFFIN1Yt/Gsp2Iooq/FcYlYnEPHb4GFzalg==", + "dev": true, + "requires": { + "@tanstack/react-router": "1.19.1", + "clsx": "^2.1.0", + "date-fns": "^2.29.1", + "goober": "^2.1.14" + } + }, + "@tanstack/router-generator": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.19.0.tgz", + "integrity": "sha512-vFF8Q7SdyygiYC7lfJ83GRif0vcxjak9SAcgtX/w7TLR0O+qdxRXFPvhKTQQXH6vVezy5Au9bSaSI2EgDD1ubA==", + "dev": true, + "requires": { + "prettier": "^3.1.1", + "zod": "^3.22.4" + } + }, + "@tanstack/router-vite-plugin": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-vite-plugin/-/router-vite-plugin-1.19.0.tgz", + "integrity": "sha512-yvvQnJ7JvqsnxAFqwiHhNTV2n1jKkidjc+XbgS2aNnEHC0aHnYH2ygPlmmfiVD7PMO7x64PdI5e12TzY/aKoFA==", + "dev": true, + "requires": { + "@tanstack/router-generator": "1.19.0" + } + }, + "@tanstack/store": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.1.3.tgz", + "integrity": "sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw==" + }, "@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + }, + "@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "requires": { + "@types/lodash": "*" + } + }, "@types/node": { "version": "20.10.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", @@ -3330,17 +6999,22 @@ "undici-types": "~5.26.4" } }, + "@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "devOptional": true }, "@types/react": { "version": "18.2.39", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", - "dev": true, + "devOptional": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3360,7 +7034,7 @@ "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "devOptional": true }, "@types/semver": { "version": "7.5.6", @@ -3483,6 +7157,24 @@ "@swc/core": "^1.3.96" } }, + "@zag-js/dom-query": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.16.0.tgz", + "integrity": "sha512-Oqhd6+biWyKnhKwFFuZrrf6lxBz2tX2pRQe6grUnYwO6HJ8BcbqZomy2lpOdr+3itlaUqx+Ywj5E5ZZDr/LBfQ==" + }, + "@zag-js/element-size": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.10.5.tgz", + "integrity": "sha512-uQre5IidULANvVkNOBQ1tfgwTQcGl4hliPSe69Fct1VfYb2Fd0jdAcGzqQgPhfrXFpR62MxLPB7erxJ/ngtL8w==" + }, + "@zag-js/focus-visible": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.16.0.tgz", + "integrity": "sha512-a7U/HSopvQbrDU4GLerpqiMcHKEkQkNPeDZJWz38cw/6Upunh41GjHetq5TB84hxyCaDzJ6q2nEdNoBQfC0FKA==", + "requires": { + "@zag-js/dom-query": "0.16.0" + } + }, "acorn": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", @@ -3496,18 +7188,6 @@ "dev": true, "requires": {} }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3529,6 +7209,14 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "requires": { + "tslib": "^2.0.0" + } + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -3550,17 +7238,30 @@ "proxy-from-env": "^1.1.0" } }, + "babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3575,6 +7276,21 @@ "fill-range": "^7.0.1" } }, + "broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "requires": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -3584,8 +7300,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camelcase": { "version": "6.3.0", @@ -3603,6 +7318,12 @@ "supports-color": "^7.1.0" } }, + "clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "dev": true + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3618,6 +7339,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3632,11 +7358,40 @@ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true }, + "compute-scroll-into-view": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", + "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } }, "cross-spawn": { "version": "7.0.3", @@ -3649,11 +7404,27 @@ "which": "^2.0.1" } }, + "css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "requires": { + "tiny-invariant": "^1.0.6" + } + }, "csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.0" + } }, "debug": { "version": "4.3.4", @@ -3675,6 +7446,16 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3693,6 +7474,14 @@ "esutils": "^2.0.2" } }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, "esbuild": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", @@ -3726,8 +7515,7 @@ "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 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "eslint": { "version": "8.54.0", @@ -3773,6 +7561,26 @@ "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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 + } } }, "eslint-plugin-react-hooks": { @@ -3915,6 +7723,11 @@ "to-regex-range": "^5.0.1" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3942,6 +7755,14 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "focus-lock": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.3.tgz", + "integrity": "sha512-hfXkZha7Xt4RQtrL1HBfspAuIj89Y0fb6GX0dfJilb8S2G/lvL4akPAcHq6xoD2NuZnDMCnZL/zQesMyeu6Psg==", + "requires": { + "tslib": "^2.0.3" + } + }, "follow-redirects": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", @@ -3957,6 +7778,47 @@ "mime-types": "^2.1.12" } }, + "framer-motion": { + "version": "10.16.16", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.16.tgz", + "integrity": "sha512-je6j91rd7NmUX7L1XHouwJ4v3R+SO4umso2LUcgOct3rHZ0PajZ80ETYZTajzEXEl9DlKyzjyt4AvGQ+lrebOw==", + "requires": { + "@emotion/is-prop-valid": "^0.8.2", + "tslib": "^2.4.0" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + } + } + }, + "framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "requires": { + "tslib": "2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, "fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -3971,8 +7833,7 @@ "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 + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.3", @@ -3981,11 +7842,20 @@ "dev": true, "optional": true }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4027,6 +7897,13 @@ "slash": "^3.0.0" } }, + "goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "dev": true, + "requires": {} + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4058,6 +7935,22 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -4068,7 +7961,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4084,7 +7976,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -4093,8 +7984,28 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "requires": { + "hasown": "^2.0.0" + } }, "is-extglob": { "version": "2.1.1", @@ -4129,6 +8040,11 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4149,6 +8065,11 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "json-schema-ref-parser": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", @@ -4158,12 +8079,6 @@ "@apidevtools/json-schema-ref-parser": "9.0.9" } }, - "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 - }, "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", @@ -4199,6 +8114,11 @@ "type-check": "~0.4.0" } }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4214,6 +8134,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4231,6 +8156,15 @@ "yallist": "^4.0.0" } }, + "match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "requires": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4247,6 +8181,11 @@ "picomatch": "^2.3.1" } }, + "microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -4264,7 +8203,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4281,6 +8219,14 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "requires": { + "big-integer": "^1.6.16" + } + }, "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -4299,11 +8245,20 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -4357,11 +8312,21 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "requires": { "callsites": "^3.0.0" } }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4371,8 +8336,7 @@ "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 + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -4380,11 +8344,15 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "picocolors": { "version": "1.0.0", @@ -4415,6 +8383,22 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4440,6 +8424,14 @@ "loose-envify": "^1.1.0" } }, + "react-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "requires": { + "@babel/runtime": "^7.12.13" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -4449,11 +8441,106 @@ "scheduler": "^0.23.0" } }, + "react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "react-focus-lock": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.11.1.tgz", + "integrity": "sha512-IXLwnTBrLTlKTpASZXqqXJ8oymWrgAlOfuuDYN4XCuN1YJ72dwX198UCaF1QqGUk5C3QOnlMik//n3ufcfe8Ig==", + "requires": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^1.3.2", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + } + }, + "react-hook-form": { + "version": "7.49.3", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", + "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==", + "requires": {} + }, + "react-icons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", + "integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==", + "requires": {} + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "requires": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + } + }, + "react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "requires": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + } + }, + "react-remove-scroll-bar": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.5.tgz", + "integrity": "sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==", + "requires": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + } + }, + "react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "requires": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + } + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "reusify": { "version": "1.0.4", @@ -4465,7 +8552,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -4565,6 +8651,11 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4574,12 +8665,32 @@ "has-flag": "^4.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, "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 }, + "tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4589,6 +8700,11 @@ "is-number": "^7.0.0" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -4596,6 +8712,11 @@ "dev": true, "requires": {} }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4636,6 +8757,15 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true }, + "unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "requires": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4645,6 +8775,29 @@ "punycode": "^2.1.0" } }, + "use-callback-ref": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", + "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", + "requires": { + "tslib": "^2.0.0" + } + }, + "use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "requires": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + } + }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "vite": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.4.tgz", @@ -4675,8 +8828,7 @@ "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 + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "yallist": { "version": "4.0.0", @@ -4684,11 +8836,30 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + }, "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 + }, + "zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "dev": true + }, + "zustand": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.0.tgz", + "integrity": "sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==", + "requires": { + "use-sync-external-store": "1.2.0" + } } } } diff --git a/new-frontend/package.json b/new-frontend/package.json index da94d40181..11b2bd72d3 100644 --- a/new-frontend/package.json +++ b/new-frontend/package.json @@ -15,7 +15,7 @@ "@chakra-ui/react": "2.8.2", "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", - "@types/react-router-dom": "5.3.3", + "@tanstack/react-router": "1.19.1", "axios": "1.6.2", "form-data": "4.0.0", "framer-motion": "10.16.16", @@ -23,10 +23,12 @@ "react-dom": "^18.2.0", "react-hook-form": "7.49.3", "react-icons": "5.0.1", - "react-router-dom": "6.21.1", + "react-query": "3.39.3", "zustand": "4.5.0" }, "devDependencies": { + "@tanstack/router-devtools": "1.19.1", + "@tanstack/router-vite-plugin": "1.19.0", "@types/node": "20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", diff --git a/new-frontend/src/components/Admin/AddUser.tsx b/new-frontend/src/components/Admin/AddUser.tsx index 4a99aa3278..68b550c0a3 100644 --- a/new-frontend/src/components/Admin/AddUser.tsx +++ b/new-frontend/src/components/Admin/AddUser.tsx @@ -2,11 +2,11 @@ import React from 'react'; import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; +import { useMutation, useQueryClient } from 'react-query'; -import { UserCreate } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; -import { useUsersStore } from '../../store/users-store'; +import { UserCreate, UsersService } from '../../client'; import { ApiError } from '../../client/core/ApiError'; +import useCustomToast from '../../hooks/useCustomToast'; interface AddUserProps { isOpen: boolean; @@ -19,6 +19,7 @@ interface UserCreateForm extends UserCreate { } const AddUser: React.FC = ({ isOpen, onClose }) => { + const queryClient = useQueryClient(); const showToast = useCustomToast(); const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm({ mode: 'onBlur', @@ -32,18 +33,28 @@ const AddUser: React.FC = ({ isOpen, onClose }) => { is_active: false } }); - const { addUser } = useUsersStore(); - const onSubmit: SubmitHandler = async (data) => { - try { - await addUser(data); + const addUser = async (data: UserCreate) => { + await UsersService.createUser({ requestBody: data }) + } + + const mutation = useMutation(addUser, { + onSuccess: () => { showToast('Success!', 'User created successfully.', 'success'); reset(); onClose(); - } catch (err) { - const errDetail = (err as ApiError).body.detail; + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail; showToast('Something went wrong.', `${errDetail}`, 'error'); + }, + onSettled: () => { + queryClient.invalidateQueries('users'); } + }); + + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data); } return ( diff --git a/new-frontend/src/components/Admin/EditUser.tsx b/new-frontend/src/components/Admin/EditUser.tsx index 06a818f803..3ca5ce0f5c 100644 --- a/new-frontend/src/components/Admin/EditUser.tsx +++ b/new-frontend/src/components/Admin/EditUser.tsx @@ -2,13 +2,13 @@ import React from 'react'; import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; +import { useMutation, useQueryClient } from 'react-query'; -import { ApiError, UserUpdate } from '../../client'; +import { ApiError, UserOut, UserUpdate, UsersService } from '../../client'; import useCustomToast from '../../hooks/useCustomToast'; -import { useUsersStore } from '../../store/users-store'; interface EditUserProps { - user_id: number; + user: UserOut; isOpen: boolean; onClose: () => void; } @@ -17,37 +17,39 @@ interface UserUpdateForm extends UserUpdate { confirm_password: string; } -const EditUser: React.FC = ({ user_id, isOpen, onClose }) => { +const EditUser: React.FC = ({ user, isOpen, onClose }) => { + const queryClient = useQueryClient(); const showToast = useCustomToast(); - const { editUser, users } = useUsersStore(); - const currentUser = users.find((user) => user.id === user_id); - const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm({ + + const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting, isDirty } } = useForm({ mode: 'onBlur', criteriaMode: 'all', - defaultValues: { - email: currentUser?.email, - full_name: currentUser?.full_name, - password: '', - confirm_password: '', - is_superuser: currentUser?.is_superuser, - is_active: currentUser?.is_active - } + defaultValues: user }); + const updateUser = async (data: UserUpdateForm) => { + await UsersService.updateUser({ userId: user.id, requestBody: data }); + } - const onSubmit: SubmitHandler = async (data) => { - try { - if (data.password === '') { - delete data.password; - } - await editUser(user_id, data); + const mutation = useMutation(updateUser, { + onSuccess: () => { showToast('Success!', 'User updated successfully.', 'success'); - reset(); onClose(); - } catch (err) { - const errDetail = (err as ApiError).body.detail; + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail; showToast('Something went wrong.', `${errDetail}`, 'error'); + }, + onSettled: () => { + queryClient.invalidateQueries('users'); + } + }); + + const onSubmit: SubmitHandler = async (data) => { + if (data.password === '') { + delete data.password; } + mutation.mutate(data) } const onCancel = () => { @@ -70,12 +72,12 @@ const EditUser: React.FC = ({ user_id, isOpen, onClose }) => { Email - + {errors.email && {errors.email.message}} Full name - + Set Password @@ -100,7 +102,7 @@ const EditUser: React.FC = ({ user_id, isOpen, onClose }) => { - diff --git a/new-frontend/src/components/Common/ActionsMenu.tsx b/new-frontend/src/components/Common/ActionsMenu.tsx index e1999d023c..625f5d7ec7 100644 --- a/new-frontend/src/components/Common/ActionsMenu.tsx +++ b/new-frontend/src/components/Common/ActionsMenu.tsx @@ -7,15 +7,16 @@ import { FiEdit, FiTrash } from 'react-icons/fi'; import EditUser from '../Admin/EditUser'; import EditItem from '../Items/EditItem'; import Delete from './DeleteAlert'; +import { ItemOut, UserOut } from '../../client'; interface ActionsMenuProps { type: string; - id: number; + value: ItemOut | UserOut; disabled?: boolean; } -const ActionsMenu: React.FC = ({ type, id, disabled }) => { +const ActionsMenu: React.FC = ({ type, value, disabled }) => { const editUserModal = useDisclosure(); const deleteModal = useDisclosure(); @@ -29,10 +30,10 @@ const ActionsMenu: React.FC = ({ type, id, disabled }) => { } color='ui.danger'>Delete {type} { - type === 'User' ? - : + type === 'User' ? + : } - + ); diff --git a/new-frontend/src/components/Common/DeleteAlert.tsx b/new-frontend/src/components/Common/DeleteAlert.tsx index f6c70cdebf..6187aff1b3 100644 --- a/new-frontend/src/components/Common/DeleteAlert.tsx +++ b/new-frontend/src/components/Common/DeleteAlert.tsx @@ -1,11 +1,11 @@ -import React, { useState } from 'react'; +import React from 'react'; import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; +import { useMutation, useQueryClient } from 'react-query'; +import { ItemsService, UsersService } from '../../client'; import useCustomToast from '../../hooks/useCustomToast'; -import { useItemsStore } from '../../store/items-store'; -import { useUsersStore } from '../../store/users-store'; interface DeleteProps { type: string; @@ -15,20 +15,36 @@ interface DeleteProps { } const Delete: React.FC = ({ type, id, isOpen, onClose }) => { + const queryClient = useQueryClient(); const showToast = useCustomToast(); const cancelRef = React.useRef(null); - const { handleSubmit, formState: {isSubmitting} } = useForm(); - const { deleteItem } = useItemsStore(); - const { deleteUser } = useUsersStore(); + const { handleSubmit, formState: { isSubmitting } } = useForm(); - const onSubmit = async () => { - try { - type === 'Item' ? await deleteItem(id) : await deleteUser(id); + const deleteEntity = async (id: number) => { + if (type === 'Item') { + await ItemsService.deleteItem({ id: id }); + } else if (type === 'User') { + await UsersService.deleteUser({ userId: id }); + } else { + throw new Error(`Unexpected type: ${type}`); + } + } + + const mutation = useMutation(deleteEntity, { + onSuccess: () => { showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success'); onClose(); - } catch (err) { + }, + onError: () => { showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error'); + }, + onSettled: () => { + queryClient.invalidateQueries(type === 'Item' ? 'items' : 'users'); } + }) + + const onSubmit = async () => { + mutation.mutate(id); } return ( @@ -37,11 +53,11 @@ const Delete: React.FC = ({ type, id, isOpen, onClose }) => { isOpen={isOpen} onClose={onClose} leastDestructiveRef={cancelRef} - size={{ base: "sm", md: "md" }} + size={{ base: 'sm', md: 'md' }} isCentered > - + Delete {type} @@ -52,7 +68,7 @@ const Delete: React.FC = ({ type, id, isOpen, onClose }) => { - diff --git a/new-frontend/src/components/Common/NotFound.tsx b/new-frontend/src/components/Common/NotFound.tsx new file mode 100644 index 0000000000..c7a78861af --- /dev/null +++ b/new-frontend/src/components/Common/NotFound.tsx @@ -0,0 +1,22 @@ +import { Button, Container, Text } from '@chakra-ui/react'; +import { Link } from '@tanstack/react-router'; + +const NotFound: React.FC = () => { + + return ( + <> + + 404 + Oops! + Page not found. + + + + ); +} + +export default NotFound; + + diff --git a/new-frontend/src/components/Common/Sidebar.tsx b/new-frontend/src/components/Common/Sidebar.tsx index e0c0e222b0..c1338dc217 100644 --- a/new-frontend/src/components/Common/Sidebar.tsx +++ b/new-frontend/src/components/Common/Sidebar.tsx @@ -2,18 +2,20 @@ import React from 'react'; import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react'; import { FiLogOut, FiMenu } from 'react-icons/fi'; +import { useQueryClient } from 'react-query'; import Logo from '../../assets/images/fastapi-logo.svg'; +import { UserOut } from '../../client'; import useAuth from '../../hooks/useAuth'; -import { useUserStore } from '../../store/user-store'; import SidebarItems from './SidebarItems'; const Sidebar: React.FC = () => { + const queryClient = useQueryClient(); const bgColor = useColorModeValue('white', '#1a202c'); const textColor = useColorModeValue('gray', 'white'); const secBgColor = useColorModeValue('ui.secondary', '#252d3d'); + const currentUser = queryClient.getQueryData('currentUser'); const { isOpen, onOpen, onClose } = useDisclosure(); - const { user } = useUserStore(); const { logout } = useAuth(); const handleLogout = async () => { @@ -40,8 +42,8 @@ const Sidebar: React.FC = () => { { - user?.email && - Logged in as: {user.email} + currentUser?.email && + Logged in as: {currentUser.email} } @@ -56,8 +58,8 @@ const Sidebar: React.FC = () => { { - user?.email && - Logged in as: {user.email} + currentUser?.email && + Logged in as: {currentUser.email} } diff --git a/new-frontend/src/components/Common/SidebarItems.tsx b/new-frontend/src/components/Common/SidebarItems.tsx index c6d71fef51..d0d881957a 100644 --- a/new-frontend/src/components/Common/SidebarItems.tsx +++ b/new-frontend/src/components/Common/SidebarItems.tsx @@ -2,14 +2,15 @@ import React from 'react'; import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'; import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi'; -import { Link, useLocation } from 'react-router-dom'; +import { Link } from '@tanstack/react-router'; +import { useQueryClient } from 'react-query'; -import { useUserStore } from '../../store/user-store'; +import { UserOut } from '../../client'; const items = [ - { icon: FiHome, title: 'Dashboard', path: "/" }, - { icon: FiBriefcase, title: 'Items', path: "/items" }, - { icon: FiSettings, title: 'User Settings', path: "/settings" }, + { icon: FiHome, title: 'Dashboard', path: '/' }, + { icon: FiBriefcase, title: 'Items', path: '/items' }, + { icon: FiSettings, title: 'User Settings', path: '/settings' }, ]; interface SidebarItemsProps { @@ -17,28 +18,31 @@ interface SidebarItemsProps { } const SidebarItems: React.FC = ({ onClose }) => { - const textColor = useColorModeValue("ui.main", "#E2E8F0"); - const bgActive = useColorModeValue("#E2E8F0", "#4A5568"); - const location = useLocation(); - const { user } = useUserStore(); + const queryClient = useQueryClient(); + const textColor = useColorModeValue('ui.main', '#E2E8F0'); + const bgActive = useColorModeValue('#E2E8F0', '#4A5568'); + const currentUser = queryClient.getQueryData('currentUser'); - const finalItems = user?.is_superuser ? [...items, { icon: FiUsers, title: 'Admin', path: "/admin" }] : items; + + const finalItems = currentUser?.is_superuser ? [...items, { icon: FiUsers, title: 'Admin', path: '/admin' }] : items; const listItems = finalItems.map((item) => ( - + {item.title} )); diff --git a/new-frontend/src/components/Common/UserMenu.tsx b/new-frontend/src/components/Common/UserMenu.tsx index 31b6928c56..c5154a9ac7 100644 --- a/new-frontend/src/components/Common/UserMenu.tsx +++ b/new-frontend/src/components/Common/UserMenu.tsx @@ -3,9 +3,9 @@ import React from 'react'; import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; import { FaUserAstronaut } from 'react-icons/fa'; import { FiLogOut, FiUser } from 'react-icons/fi'; -import { Link } from 'react-router-dom'; import useAuth from '../../hooks/useAuth'; +import { Link } from '@tanstack/react-router'; const UserMenu: React.FC = () => { const { logout } = useAuth(); diff --git a/new-frontend/src/components/Items/AddItem.tsx b/new-frontend/src/components/Items/AddItem.tsx index 3c62a87c94..8cb0a67223 100644 --- a/new-frontend/src/components/Items/AddItem.tsx +++ b/new-frontend/src/components/Items/AddItem.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { Button, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; +import { useMutation, useQueryClient } from 'react-query'; -import { ApiError, ItemCreate } from '../../client'; +import { ApiError, ItemCreate, ItemsService } from '../../client'; import useCustomToast from '../../hooks/useCustomToast'; -import { useItemsStore } from '../../store/items-store'; interface AddItemProps { isOpen: boolean; @@ -13,6 +13,7 @@ interface AddItemProps { } const AddItem: React.FC = ({ isOpen, onClose }) => { + const queryClient = useQueryClient(); const showToast = useCustomToast(); const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm({ mode: 'onBlur', @@ -22,19 +23,29 @@ const AddItem: React.FC = ({ isOpen, onClose }) => { description: '', }, }); - const { addItem } = useItemsStore(); - const onSubmit: SubmitHandler = async (data) => { - try { - await addItem(data); + const addItem = async (data: ItemCreate) => { + await ItemsService.createItem({ requestBody: data }) + } + + const mutation = useMutation(addItem, { + onSuccess: () => { showToast('Success!', 'Item created successfully.', 'success'); reset(); onClose(); - } catch (err) { - const errDetail = (err as ApiError).body.detail; + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail; showToast('Something went wrong.', `${errDetail}`, 'error'); + }, + onSettled: () => { + queryClient.invalidateQueries('items'); } - }; + }); + + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data); + } return ( <> diff --git a/new-frontend/src/components/Items/EditItem.tsx b/new-frontend/src/components/Items/EditItem.tsx index d72d3ef780..44f4dfb58b 100644 --- a/new-frontend/src/components/Items/EditItem.tsx +++ b/new-frontend/src/components/Items/EditItem.tsx @@ -1,34 +1,47 @@ import React from 'react'; -import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import { Button, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { ApiError, ItemUpdate } from '../../client'; +import { useMutation, useQueryClient } from 'react-query'; +import { ApiError, ItemOut, ItemUpdate, ItemsService } from '../../client'; import useCustomToast from '../../hooks/useCustomToast'; -import { useItemsStore } from '../../store/items-store'; interface EditItemProps { - id: number; + item: ItemOut; isOpen: boolean; onClose: () => void; } -const EditItem: React.FC = ({ id, isOpen, onClose }) => { +const EditItem: React.FC = ({ item, isOpen, onClose }) => { + const queryClient = useQueryClient(); const showToast = useCustomToast(); - const { editItem, items } = useItemsStore(); - const currentItem = items.find((item) => item.id === id); - const { register, handleSubmit, reset, formState: { isSubmitting }, } = useForm({ defaultValues: { title: currentItem?.title, description: currentItem?.description } }); + const { register, handleSubmit, reset, formState: { isSubmitting, errors, isDirty } } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: item + }); - const onSubmit: SubmitHandler = async (data) => { - try { - await editItem(id, data); + const updateItem = async (data: ItemUpdate) => { + await ItemsService.updateItem({ id: item.id, requestBody: data }); + } + + const mutation = useMutation(updateItem, { + onSuccess: () => { showToast('Success!', 'Item updated successfully.', 'success'); - reset(); onClose(); - } catch (err) { - const errDetail = (err as ApiError).body.detail; + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail; showToast('Something went wrong.', `${errDetail}`, 'error'); + }, + onSettled: () => { + queryClient.invalidateQueries('items'); } + }); + + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) } const onCancel = () => { @@ -49,9 +62,10 @@ const EditItem: React.FC = ({ id, isOpen, onClose }) => { Edit Item - + Title - + + {errors.title && {errors.title.message}} Description @@ -59,7 +73,7 @@ const EditItem: React.FC = ({ id, isOpen, onClose }) => { - diff --git a/new-frontend/src/components/UserSettings/ChangePassword.tsx b/new-frontend/src/components/UserSettings/ChangePassword.tsx index eb7ca0ad30..355d2c0cae 100644 --- a/new-frontend/src/components/UserSettings/ChangePassword.tsx +++ b/new-frontend/src/components/UserSettings/ChangePassword.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { Box, Button, Container, FormControl, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; +import { Box, Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { ApiError, UpdatePassword } from '../../client'; +import { useMutation } from 'react-query'; + +import { ApiError, UpdatePassword, UsersService } from '../../client'; import useCustomToast from '../../hooks/useCustomToast'; -import { useUserStore } from '../../store/user-store'; interface UpdatePasswordForm extends UpdatePassword { confirm_password: string; @@ -13,19 +14,28 @@ interface UpdatePasswordForm extends UpdatePassword { const ChangePassword: React.FC = () => { const color = useColorModeValue('gray.700', 'white'); const showToast = useCustomToast(); - const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm(); - const { editPassword } = useUserStore(); + const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm({ + mode: 'onBlur', + criteriaMode: 'all' + }); - const onSubmit: SubmitHandler = async (data) => { - try { - await editPassword(data); + const UpdatePassword = async (data: UpdatePassword) => { + await UsersService.updatePasswordMe({ requestBody: data }) + } + + const mutation = useMutation(UpdatePassword, { + onSuccess: () => { showToast('Success!', 'Password updated.', 'success'); reset(); - } catch (err) { - const errDetail = (err as ApiError).body.detail; + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail; showToast('Something went wrong.', `${errDetail}`, 'error'); } + }) + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data); } return ( @@ -35,17 +45,23 @@ const ChangePassword: React.FC = () => { Change Password - - Current password - + + Current password + + {errors.current_password && {errors.current_password.message}} - - New password - + + Set Password + + {errors.new_password && {errors.new_password.message}} - - Confirm new password - + + Confirm Password + value === getValues().new_password || 'The passwords do not match' + })} placeholder='Password' type='password' /> + {errors.confirm_password && {errors.confirm_password.message}} diff --git a/new-frontend/src/hooks/useAuth.tsx b/new-frontend/src/hooks/useAuth.ts similarity index 51% rename from new-frontend/src/hooks/useAuth.tsx rename to new-frontend/src/hooks/useAuth.ts index 29f1020bb5..2d21948bbd 100644 --- a/new-frontend/src/hooks/useAuth.tsx +++ b/new-frontend/src/hooks/useAuth.ts @@ -1,37 +1,32 @@ -import { useUserStore } from '../store/user-store'; -import { Body_login_login_access_token as AccessToken, LoginService } from '../client'; -import { useUsersStore } from '../store/users-store'; -import { useItemsStore } from '../store/items-store'; -import { useNavigate } from 'react-router-dom'; +import { useQuery } from 'react-query'; +import { useNavigate } from '@tanstack/react-router'; + +import { Body_login_login_access_token as AccessToken, LoginService, UserOut, UsersService } from '../client'; const isLoggedIn = () => { return localStorage.getItem('access_token') !== null; }; const useAuth = () => { - const { getUser, resetUser } = useUserStore(); - const { resetUsers } = useUsersStore(); - const { resetItems } = useItemsStore(); const navigate = useNavigate(); + const { data: user, isLoading } = useQuery('currentUser', UsersService.readUserMe, { + enabled: isLoggedIn(), + }); const login = async (data: AccessToken) => { const response = await LoginService.loginAccessToken({ formData: data, }); localStorage.setItem('access_token', response.access_token); - await getUser(); - navigate('/'); + navigate({ to: '/' }); }; const logout = () => { localStorage.removeItem('access_token'); - resetUser(); - resetUsers(); - resetItems(); - navigate('/login'); + navigate({ to: '/login' }); }; - return { login, logout }; + return { login, logout, user, isLoading }; } export { isLoggedIn }; diff --git a/new-frontend/src/hooks/useCustomToast.tsx b/new-frontend/src/hooks/useCustomToast.ts similarity index 92% rename from new-frontend/src/hooks/useCustomToast.tsx rename to new-frontend/src/hooks/useCustomToast.ts index 41e4e78844..2483fd3b4c 100644 --- a/new-frontend/src/hooks/useCustomToast.tsx +++ b/new-frontend/src/hooks/useCustomToast.ts @@ -11,6 +11,7 @@ const useCustomToast = () => { description, status, isClosable: true, + position: 'bottom-right' }); }, [toast]); diff --git a/new-frontend/src/main.tsx b/new-frontend/src/main.tsx index f49e1f3d42..a7a730517d 100644 --- a/new-frontend/src/main.tsx +++ b/new-frontend/src/main.tsx @@ -1,35 +1,33 @@ -import React from 'react'; import ReactDOM from 'react-dom/client'; - -import { ChakraProvider } from '@chakra-ui/provider'; -import { createStandaloneToast } from '@chakra-ui/toast'; -import { RouterProvider, createBrowserRouter } from 'react-router-dom'; +import { ChakraProvider } from '@chakra-ui/react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { RouterProvider, createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' import { OpenAPI } from './client'; -import { isLoggedIn } from './hooks/useAuth'; -import privateRoutes from './routes/private_route'; -import publicRoutes from './routes/public_route'; import theme from './theme'; - +import { StrictMode } from 'react'; OpenAPI.BASE = import.meta.env.VITE_API_URL; OpenAPI.TOKEN = async () => { return localStorage.getItem('access_token') || ''; } -const router = createBrowserRouter([ - isLoggedIn() ? privateRoutes() : {}, - ...publicRoutes(), -]); +const queryClient = new QueryClient(); -const { ToastContainer } = createStandaloneToast(); +const router = createRouter({ routeTree }) +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} ReactDOM.createRoot(document.getElementById('root')!).render( - + - - + + + - , -) - + +); \ No newline at end of file diff --git a/new-frontend/src/pages/Dashboard.tsx b/new-frontend/src/pages/Dashboard.tsx deleted file mode 100644 index 9bbb46a082..0000000000 --- a/new-frontend/src/pages/Dashboard.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -import { Container, Text } from '@chakra-ui/react'; - -import { useUserStore } from '../store/user-store'; - - -const Dashboard: React.FC = () => { - const { user } = useUserStore(); - - return ( - <> - - Hi, {user?.full_name || user?.email} 👋🏼 - Welcome back, nice to see you again! - - - - ) -} - -export default Dashboard; \ No newline at end of file diff --git a/new-frontend/src/pages/ErrorPage.tsx b/new-frontend/src/pages/ErrorPage.tsx deleted file mode 100644 index cc744df2cc..0000000000 --- a/new-frontend/src/pages/ErrorPage.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Button, Container, Text } from '@chakra-ui/react'; -import { Link, useRouteError } from 'react-router-dom'; - -const ErrorPage: React.FC = () => { - const error = useRouteError(); - console.log(error); - - return ( - <> - - Oops! - Houston, we have a problem. - An unexpected error has occurred. - {/* {error.statusText || error.message} */} - - - - ); -} - -export default ErrorPage; - - diff --git a/new-frontend/src/pages/Layout.tsx b/new-frontend/src/pages/Layout.tsx deleted file mode 100644 index ac35d2f777..0000000000 --- a/new-frontend/src/pages/Layout.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { useEffect } from 'react'; - -import { Flex } from '@chakra-ui/react'; -import { Outlet } from 'react-router-dom'; - -import Sidebar from '../components/Common/Sidebar'; -import UserMenu from '../components/Common/UserMenu'; -import { useUserStore } from '../store/user-store'; -import { isLoggedIn } from '../hooks/useAuth'; - -const Layout: React.FC = () => { - const { getUser } = useUserStore(); - - useEffect(() => { - const fetchUser = async () => { - if (isLoggedIn()) { - await getUser(); - } - }; - fetchUser(); - }, []); - - return ( - - - - - - ); -}; - -export default Layout; \ No newline at end of file diff --git a/new-frontend/src/routeTree.gen.ts b/new-frontend/src/routeTree.gen.ts new file mode 100644 index 0000000000..395866a44b --- /dev/null +++ b/new-frontend/src/routeTree.gen.ts @@ -0,0 +1,118 @@ +/* prettier-ignore-start */ + +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file is auto-generated by TanStack Router + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as ResetPasswordImport } from './routes/reset-password' +import { Route as RecoverPasswordImport } from './routes/recover-password' +import { Route as LoginImport } from './routes/login' +import { Route as LayoutImport } from './routes/_layout' +import { Route as LayoutIndexImport } from './routes/_layout/index' +import { Route as LayoutSettingsImport } from './routes/_layout/settings' +import { Route as LayoutItemsImport } from './routes/_layout/items' +import { Route as LayoutAdminImport } from './routes/_layout/admin' + +// Create/Update Routes + +const ResetPasswordRoute = ResetPasswordImport.update({ + path: '/reset-password', + getParentRoute: () => rootRoute, +} as any) + +const RecoverPasswordRoute = RecoverPasswordImport.update({ + path: '/recover-password', + getParentRoute: () => rootRoute, +} as any) + +const LoginRoute = LoginImport.update({ + path: '/login', + getParentRoute: () => rootRoute, +} as any) + +const LayoutRoute = LayoutImport.update({ + id: '/_layout', + getParentRoute: () => rootRoute, +} as any) + +const LayoutIndexRoute = LayoutIndexImport.update({ + path: '/', + getParentRoute: () => LayoutRoute, +} as any) + +const LayoutSettingsRoute = LayoutSettingsImport.update({ + path: '/settings', + getParentRoute: () => LayoutRoute, +} as any) + +const LayoutItemsRoute = LayoutItemsImport.update({ + path: '/items', + getParentRoute: () => LayoutRoute, +} as any) + +const LayoutAdminRoute = LayoutAdminImport.update({ + path: '/admin', + getParentRoute: () => LayoutRoute, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_layout': { + preLoaderRoute: typeof LayoutImport + parentRoute: typeof rootRoute + } + '/login': { + preLoaderRoute: typeof LoginImport + parentRoute: typeof rootRoute + } + '/recover-password': { + preLoaderRoute: typeof RecoverPasswordImport + parentRoute: typeof rootRoute + } + '/reset-password': { + preLoaderRoute: typeof ResetPasswordImport + parentRoute: typeof rootRoute + } + '/_layout/admin': { + preLoaderRoute: typeof LayoutAdminImport + parentRoute: typeof LayoutImport + } + '/_layout/items': { + preLoaderRoute: typeof LayoutItemsImport + parentRoute: typeof LayoutImport + } + '/_layout/settings': { + preLoaderRoute: typeof LayoutSettingsImport + parentRoute: typeof LayoutImport + } + '/_layout/': { + preLoaderRoute: typeof LayoutIndexImport + parentRoute: typeof LayoutImport + } + } +} + +// Create and export the route tree + +export const routeTree = rootRoute.addChildren([ + LayoutRoute.addChildren([ + LayoutAdminRoute, + LayoutItemsRoute, + LayoutSettingsRoute, + LayoutIndexRoute, + ]), + LoginRoute, + RecoverPasswordRoute, + ResetPasswordRoute, +]) + +/* prettier-ignore-end */ diff --git a/new-frontend/src/routes/__root.tsx b/new-frontend/src/routes/__root.tsx new file mode 100644 index 0000000000..d78cf139e4 --- /dev/null +++ b/new-frontend/src/routes/__root.tsx @@ -0,0 +1,13 @@ +import { createRootRoute, Outlet } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/router-devtools' +import NotFound from '../components/Common/NotFound' + +export const Route = createRootRoute({ + component: () => ( + <> + + + + ), + notFoundComponent: () => , +}) \ No newline at end of file diff --git a/new-frontend/src/routes/_layout.tsx b/new-frontend/src/routes/_layout.tsx new file mode 100644 index 0000000000..d1f48e6a21 --- /dev/null +++ b/new-frontend/src/routes/_layout.tsx @@ -0,0 +1,38 @@ +import { Flex, Spinner } from '@chakra-ui/react'; +import { Outlet, createFileRoute, redirect } from '@tanstack/react-router'; + +import Sidebar from '../components/Common/Sidebar'; +import UserMenu from '../components/Common/UserMenu'; +import useAuth, { isLoggedIn } from '../hooks/useAuth'; + + +export const Route = createFileRoute('/_layout')({ + component: Layout, + beforeLoad: async () => { + if (!isLoggedIn()) { + throw redirect({ + to: '/login', + }) + } + } +}) + +function Layout() { + const { isLoading } = useAuth(); + + return ( + + + {isLoading ? ( + + + + ) : ( + + )} + + + ); +}; + +export default Layout; \ No newline at end of file diff --git a/new-frontend/src/pages/Admin.tsx b/new-frontend/src/routes/_layout/admin.tsx similarity index 71% rename from new-frontend/src/pages/Admin.tsx rename to new-frontend/src/routes/_layout/admin.tsx index 69ec315cd0..b3d6a90616 100644 --- a/new-frontend/src/pages/Admin.tsx +++ b/new-frontend/src/routes/_layout/admin.tsx @@ -1,36 +1,26 @@ -import React, { useEffect, useState } from 'react'; - import { Badge, Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; +import { createFileRoute } from '@tanstack/react-router'; +import { useQuery, useQueryClient } from 'react-query'; + +import { ApiError, UserOut, UsersService } from '../../client'; +import ActionsMenu from '../../components/Common/ActionsMenu'; +import Navbar from '../../components/Common/Navbar'; +import useCustomToast from '../../hooks/useCustomToast'; -import { ApiError } from '../client'; -import ActionsMenu from '../components/Common/ActionsMenu'; -import Navbar from '../components/Common/Navbar'; -import useCustomToast from '../hooks/useCustomToast'; -import { useUserStore } from '../store/user-store'; -import { useUsersStore } from '../store/users-store'; +export const Route = createFileRoute('/_layout/admin')({ + component: Admin, +}) -const Admin: React.FC = () => { +function Admin() { + const queryClient = useQueryClient(); const showToast = useCustomToast(); - const [isLoading, setIsLoading] = useState(false); - const { users, getUsers } = useUsersStore(); - const { user: currentUser } = useUserStore(); + const currentUser = queryClient.getQueryData('currentUser'); + const { data: users, isLoading, isError, error } = useQuery('users', () => UsersService.readUsers({})) - useEffect(() => { - const fetchUsers = async () => { - setIsLoading(true); - try { - await getUsers(); - } catch (err) { - const errDetail = (err as ApiError).body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } finally { - setIsLoading(false); - } - } - if (users.length === 0) { - fetchUsers(); - } - }, []) + if (isError) { + const errDetail = (error as ApiError).body?.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); + } return ( <> @@ -58,7 +48,7 @@ const Admin: React.FC = () => {
- {users.map((user) => ( + {users.data.map((user) => ( @@ -76,7 +66,7 @@ const Admin: React.FC = () => { ))} diff --git a/new-frontend/src/routes/_layout/index.tsx b/new-frontend/src/routes/_layout/index.tsx new file mode 100644 index 0000000000..6901801a21 --- /dev/null +++ b/new-frontend/src/routes/_layout/index.tsx @@ -0,0 +1,27 @@ + +import { Container, Text } from '@chakra-ui/react'; +import { useQueryClient } from 'react-query'; +import { createFileRoute } from '@tanstack/react-router'; + +import { UserOut } from '../../client'; + +export const Route = createFileRoute('/_layout/')({ + component: Dashboard, +}) + +function Dashboard() { + const queryClient = useQueryClient(); + + const currentUser = queryClient.getQueryData('currentUser'); + + return ( + <> + + Hi, {currentUser?.full_name || currentUser?.email} 👋🏼 + Welcome back, nice to see you again! + + + ) +} + +export default Dashboard; \ No newline at end of file diff --git a/new-frontend/src/pages/Items.tsx b/new-frontend/src/routes/_layout/items.tsx similarity index 65% rename from new-frontend/src/pages/Items.tsx rename to new-frontend/src/routes/_layout/items.tsx index 3aab0d21a0..7a2960911d 100644 --- a/new-frontend/src/pages/Items.tsx +++ b/new-frontend/src/routes/_layout/items.tsx @@ -1,35 +1,24 @@ -import React, { useEffect, useState } from 'react'; - import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; +import { createFileRoute } from '@tanstack/react-router'; +import { useQuery } from 'react-query'; -import { ApiError } from '../client'; -import ActionsMenu from '../components/Common/ActionsMenu'; -import Navbar from '../components/Common/Navbar'; -import useCustomToast from '../hooks/useCustomToast'; -import { useItemsStore } from '../store/items-store'; +import { ApiError, ItemsService } from '../../client'; +import ActionsMenu from '../../components/Common/ActionsMenu'; +import Navbar from '../../components/Common/Navbar'; +import useCustomToast from '../../hooks/useCustomToast'; -const Items: React.FC = () => { - const showToast = useCustomToast(); - const [isLoading, setIsLoading] = useState(false); - const { items, getItems } = useItemsStore(); +export const Route = createFileRoute('/_layout/items')({ + component: Items, +}) - useEffect(() => { - const fetchItems = async () => { - setIsLoading(true); - try { - await getItems(); - } catch (err) { - const errDetail = (err as ApiError).body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } finally { - setIsLoading(false); - } - } - if (items.length === 0) { - fetchItems(); - } - }, []) +function Items() { + const showToast = useCustomToast(); + const { data: items, isLoading, isError, error } = useQuery('items', () => ItemsService.readItems({})) + if (isError) { + const errDetail = (error as ApiError).body?.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); + } return ( <> @@ -56,13 +45,13 @@ const Items: React.FC = () => { - {items.map((item) => ( + {items.data.map((item) => ( ))} diff --git a/new-frontend/src/pages/UserSettings.tsx b/new-frontend/src/routes/_layout/settings.tsx similarity index 58% rename from new-frontend/src/pages/UserSettings.tsx rename to new-frontend/src/routes/_layout/settings.tsx index ea096cf77e..14d9e6a96e 100644 --- a/new-frontend/src/pages/UserSettings.tsx +++ b/new-frontend/src/routes/_layout/settings.tsx @@ -1,11 +1,12 @@ -import React from 'react'; - import { Container, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; -import Appearance from '../components/UserSettings/Appearance'; -import ChangePassword from '../components/UserSettings/ChangePassword'; -import DeleteAccount from '../components/UserSettings/DeleteAccount'; -import UserInformation from '../components/UserSettings/UserInformation'; -import { useUserStore } from '../store/user-store'; +import { createFileRoute } from '@tanstack/react-router'; +import { useQueryClient } from 'react-query'; + +import { UserOut } from '../../client'; +import Appearance from '../../components/UserSettings/Appearance'; +import ChangePassword from '../../components/UserSettings/ChangePassword'; +import DeleteAccount from '../../components/UserSettings/DeleteAccount'; +import UserInformation from '../../components/UserSettings/UserInformation'; const tabsConfig = [ { title: 'My profile', component: UserInformation }, @@ -14,11 +15,14 @@ const tabsConfig = [ { title: 'Danger zone', component: DeleteAccount }, ]; -const UserSettings: React.FC = () => { - const { user } = useUserStore(); - - const finalTabs = user?.is_superuser ? tabsConfig.slice(0, 3) : tabsConfig; +export const Route = createFileRoute('/_layout/settings')({ + component: UserSettings, +}) +function UserSettings() { + const queryClient = useQueryClient(); + const currentUser = queryClient.getQueryData('currentUser'); + const finalTabs = currentUser?.is_superuser ? tabsConfig.slice(0, 3) : tabsConfig; return ( @@ -41,6 +45,6 @@ const UserSettings: React.FC = () => { ); -}; +} export default UserSettings; \ No newline at end of file diff --git a/new-frontend/src/pages/Login.tsx b/new-frontend/src/routes/login.tsx similarity index 87% rename from new-frontend/src/pages/Login.tsx rename to new-frontend/src/routes/login.tsx index 4563c6db32..ce4b22931a 100644 --- a/new-frontend/src/pages/Login.tsx +++ b/new-frontend/src/routes/login.tsx @@ -2,16 +2,28 @@ import React from 'react'; import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'; import { Button, Center, Container, FormControl, FormErrorMessage, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react'; +import { Link as RouterLink, createFileRoute, redirect } from '@tanstack/react-router'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { Link as ReactRouterLink } from 'react-router-dom'; import Logo from '../assets/images/fastapi-logo.svg'; import { ApiError } from '../client'; import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token'; -import useAuth from '../hooks/useAuth'; +import useAuth, { isLoggedIn } from '../hooks/useAuth'; -const Login: React.FC = () => { +export const Route = createFileRoute('/login')({ + component: Login, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: '/', + }) + } + } +}) + +function Login() { const [show, setShow] = useBoolean(); + const { login } = useAuth(); const [error, setError] = React.useState(null); const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ mode: 'onBlur', @@ -21,7 +33,6 @@ const Login: React.FC = () => { password: '' } }); - const { login } = useAuth(); const onSubmit: SubmitHandler = async (data) => { try { @@ -73,7 +84,7 @@ const Login: React.FC = () => { }
- + Forgot password?
diff --git a/new-frontend/src/routes/private_route.tsx b/new-frontend/src/routes/private_route.tsx deleted file mode 100644 index 8fbe34f63c..0000000000 --- a/new-frontend/src/routes/private_route.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Admin from '../pages/Admin'; -import Dashboard from '../pages/Dashboard'; -import ErrorPage from '../pages/ErrorPage'; -import Items from '../pages/Items'; -import Layout from '../pages/Layout'; -import UserSettings from '../pages/UserSettings'; - -export default function privateRoutes() { - - return { - path: '/', - element: , - errorElement: , - children: [ - { path: '/', element: }, - { path: 'items', element: }, - { path: 'admin', element: }, - { path: 'settings', element: }, - ], - }; -} diff --git a/new-frontend/src/routes/public_route.tsx b/new-frontend/src/routes/public_route.tsx deleted file mode 100644 index cffa5a3311..0000000000 --- a/new-frontend/src/routes/public_route.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import ErrorPage from '../pages/ErrorPage'; -import Login from '../pages/Login'; -import RecoverPassword from '../pages/RecoverPassword'; -import ResetPassword from '../pages/ResetPassword'; - -export default function publicRoutes() { - return [ - { path: '/login', element: , errorElement: }, - { path: 'recover-password', element: , errorElement: }, - { path: 'reset-password', element: , errorElement: }, - // TODO: complete this - // { path: '*', element: } - ]; -} - diff --git a/new-frontend/src/pages/RecoverPassword.tsx b/new-frontend/src/routes/recover-password.tsx similarity index 53% rename from new-frontend/src/pages/RecoverPassword.tsx rename to new-frontend/src/routes/recover-password.tsx index 1ed681f24e..f29b2ebed6 100644 --- a/new-frontend/src/pages/RecoverPassword.tsx +++ b/new-frontend/src/routes/recover-password.tsx @@ -1,16 +1,27 @@ -import React from "react"; +import { Button, Container, FormControl, FormErrorMessage, Heading, Input, Text } from '@chakra-ui/react'; +import { createFileRoute, redirect } from '@tanstack/react-router'; +import { SubmitHandler, useForm } from 'react-hook-form'; -import { Button, Container, FormControl, FormErrorMessage, Heading, Input, Text } from "@chakra-ui/react"; -import { SubmitHandler, useForm } from "react-hook-form"; - -import { LoginService } from "../client"; -import useCustomToast from "../hooks/useCustomToast"; +import { LoginService } from '../client'; +import useCustomToast from '../hooks/useCustomToast'; +import { isLoggedIn } from '../hooks/useAuth'; interface FormData { email: string; } -const RecoverPassword: React.FC = () => { +export const Route = createFileRoute('/recover-password')({ + component: RecoverPassword, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: '/', + }) + } + } +}) + +function RecoverPassword() { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm(); const showToast = useCustomToast(); @@ -18,32 +29,31 @@ const RecoverPassword: React.FC = () => { await LoginService.recoverPassword({ email: data.email, }); - - showToast("Email sent.", "We sent an email with a link to get back into your account.", "success"); + showToast('Email sent.', 'We sent an email with a link to get back into your account.', 'success'); }; return ( - + Password Recovery - + A password recovery email will be sent to the registered account. {errors.email && {errors.email.message}} - diff --git a/new-frontend/src/pages/ResetPassword.tsx b/new-frontend/src/routes/reset-password.tsx similarity index 56% rename from new-frontend/src/pages/ResetPassword.tsx rename to new-frontend/src/routes/reset-password.tsx index e80f2e9676..5be798e0f4 100644 --- a/new-frontend/src/pages/ResetPassword.tsx +++ b/new-frontend/src/routes/reset-password.tsx @@ -1,16 +1,29 @@ -import React from "react"; -import { Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, Text } from "@chakra-ui/react"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, Text } from '@chakra-ui/react'; +import { createFileRoute, redirect } from '@tanstack/react-router'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { useMutation } from 'react-query'; -import { LoginService, NewPassword } from "../client"; -import useCustomToast from "../hooks/useCustomToast"; +import { ApiError, LoginService, NewPassword } from '../client'; +import { isLoggedIn } from '../hooks/useAuth'; +import useCustomToast from '../hooks/useCustomToast'; interface NewPasswordForm extends NewPassword { confirm_password: string; } -const ResetPassword: React.FC = () => { +export const Route = createFileRoute('/reset-password')({ + component: ResetPassword, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: '/', + }) + } + } +}) + +function ResetPassword() { const { register, handleSubmit, getValues, formState: { errors } } = useForm({ mode: 'onBlur', criteriaMode: 'all', @@ -20,33 +33,43 @@ const ResetPassword: React.FC = () => { }); const showToast = useCustomToast(); - const onSubmit: SubmitHandler = async (data) => { - try { - const token = new URLSearchParams(window.location.search).get('token'); - await LoginService.resetPassword({ - requestBody: { new_password: data.new_password, token: token! } - }); - showToast("Password reset.", "Your password has been reset successfully.", "success"); - } catch (error) { - showToast("Error", "An error occurred while resetting your password.", "error"); + const resetPassword = async (data: NewPassword) => { + const token = new URLSearchParams(window.location.search).get('token'); + await LoginService.resetPassword({ + requestBody: { new_password: data.new_password, token: token! } + }); + } + + const mutation = useMutation(resetPassword, { + onSuccess: () => { + showToast('Success!', 'Password updated.', 'success'); + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } + }) + + + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data); }; return ( - + Reset Password - + Please enter your new password and confirm it to reset your password. @@ -62,7 +85,7 @@ const ResetPassword: React.FC = () => { })} placeholder='Password' type='password' /> {errors.confirm_password && {errors.confirm_password.message}} - diff --git a/new-frontend/src/store/items-store.tsx b/new-frontend/src/store/items-store.tsx deleted file mode 100644 index ddeea8c1ed..0000000000 --- a/new-frontend/src/store/items-store.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { create } from 'zustand'; -import { ItemCreate, ItemOut, ItemUpdate, ItemsService } from '../client'; - -interface ItemsStore { - items: ItemOut[]; - getItems: () => Promise; - addItem: (item: ItemCreate) => Promise; - editItem: (id: number, item: ItemUpdate) => Promise; - deleteItem: (id: number) => Promise; - resetItems: () => void; -} - -export const useItemsStore = create((set) => ({ - items: [], - getItems: async () => { - const itemsResponse = await ItemsService.readItems({ skip: 0, limit: 10 }); - set({ items: itemsResponse.data }); - }, - addItem: async (item: ItemCreate) => { - const itemResponse = await ItemsService.createItem({ requestBody: item }); - set((state) => ({ items: [...state.items, itemResponse] })); - }, - editItem: async (id: number, item: ItemUpdate) => { - const itemResponse = await ItemsService.updateItem({ id: id, requestBody: item }); - set((state) => ({ - items: state.items.map((item) => (item.id === id ? itemResponse : item)) - })); - }, - deleteItem: async (id: number) => { - await ItemsService.deleteItem({ id }); - set((state) => ({ items: state.items.filter((item) => item.id !== id) })); - }, - resetItems: () => { - set({ items: [] }); - } -})); \ No newline at end of file diff --git a/new-frontend/src/store/user-store.tsx b/new-frontend/src/store/user-store.tsx deleted file mode 100644 index 4e41dd9937..0000000000 --- a/new-frontend/src/store/user-store.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { create } from 'zustand'; -import { UpdatePassword, UserOut, UserUpdateMe, UsersService } from '../client'; - -interface UserStore { - user: UserOut | null; - getUser: () => Promise; - editUser: (user: UserUpdateMe) => Promise; - editPassword: (password: UpdatePassword) => Promise; - resetUser: () => void; -} - -export const useUserStore = create((set) => ({ - user: null, - getUser: async () => { - const user = await UsersService.readUserMe(); - set({ user }); - }, - editUser: async (user: UserUpdateMe) => { - const updatedUser = await UsersService.updateUserMe({ requestBody: user }); - set((state) => ({ user: { ...state.user, ...updatedUser } })); - }, - editPassword: async (password: UpdatePassword) => { - await UsersService.updatePasswordMe({ requestBody: password }); - }, - resetUser: () => { - set({ user: null }); - } -})); \ No newline at end of file diff --git a/new-frontend/src/store/users-store.tsx b/new-frontend/src/store/users-store.tsx deleted file mode 100644 index 18886973f4..0000000000 --- a/new-frontend/src/store/users-store.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { create } from "zustand"; -import { UserCreate, UserOut, UserUpdate, UsersService } from "../client"; - -interface UsersStore { - users: UserOut[]; - getUsers: () => Promise; - addUser: (user: UserCreate) => Promise; - editUser: (id: number, user: UserUpdate) => Promise; - deleteUser: (id: number) => Promise; - resetUsers: () => void; -} - -export const useUsersStore = create((set) => ({ - users: [], - getUsers: async () => { - const usersResponse = await UsersService.readUsers({ skip: 0, limit: 10 }); - set({ users: usersResponse.data }); - }, - addUser: async (user: UserCreate) => { - const userResponse = await UsersService.createUser({ requestBody: user }); - set((state) => ({ users: [...state.users, userResponse] })); - }, - editUser: async (id: number, user: UserUpdate) => { - const userResponse = await UsersService.updateUser({ userId: id, requestBody: user }); - set((state) => ({ - users: state.users.map((user) => (user.id === id ? userResponse : user)) - })); - }, - deleteUser: async (id: number) => { - await UsersService.deleteUser({ userId: id }); - set((state) => ({ users: state.users.filter((user) => user.id !== id) })); - }, - resetUsers: () => { - set({ users: [] }); - } -})) \ No newline at end of file diff --git a/new-frontend/vite.config.ts b/new-frontend/vite.config.ts index 861b04b356..c2fbe94324 100644 --- a/new-frontend/vite.config.ts +++ b/new-frontend/vite.config.ts @@ -1,7 +1,8 @@ -import { defineConfig } from 'vite' import react from '@vitejs/plugin-react-swc' +import { TanStackRouterVite } from '@tanstack/router-vite-plugin' +import { defineConfig } from 'vite' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), TanStackRouterVite()], }) From 3ec2e3031258224c96cfb534fe9ddaa1726b1d00 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Mar 2024 18:16:47 +0000 Subject: [PATCH 233/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 933d7e2221..c5eea89cd0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -11,6 +11,7 @@ ### Features +* ✨ Migrate to TanStack Query (React Query) and TanStack Router. PR [#637](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/637) by [@alejsdev](https://github.com/alejsdev). * ✅ Add setup and teardown database for tests. PR [#626](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/626) by [@estebanx64](https://github.com/estebanx64). * ✨ Update new-frontend client. PR [#625](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/625) by [@alejsdev](https://github.com/alejsdev). * ✨ Add password reset functionality. PR [#624](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/624) by [@alejsdev](https://github.com/alejsdev). From e22c01fb6eb0d74e9c6bbe36ddd5f4c30a7d1443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 7 Mar 2024 20:49:43 +0100 Subject: [PATCH 234/771] =?UTF-8?q?=F0=9F=93=9D=20Refactor=20README=20into?= =?UTF-8?q?=20separate=20README.md=20files=20for=20backend,=20frontend,=20?= =?UTF-8?q?deployment,=20development=20(#639)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 413 +---------------------------------------- backend/README.md | 208 +++++++++++++++++++++ deployment.md | 25 +++ development.md | 132 +++++++++++++ new-frontend/README.md | 45 ++++- scripts/test.sh | 18 +- 6 files changed, 418 insertions(+), 423 deletions(-) create mode 100644 backend/README.md create mode 100644 deployment.md create mode 100644 development.md diff --git a/README.md b/README.md index b15130416f..ae468dac79 100644 --- a/README.md +++ b/README.md @@ -141,10 +141,6 @@ The input variables, with their default values (some auto generated) are: - `pgadmin_default_password`: (default: `"changethis"`) The default user password for pgAdmin, stored in .env. - `sentry_dsn`: (default: "") The DSN for Sentry, if you are using it, you can set it later in .env. -## How to deploy - -Deploy using Docker Compose and Traefik as a reverse proxy / load balancer handling automatic HTTPS certificates. - ## Release Notes Check the file [release-notes.md](./release-notes.md). @@ -157,413 +153,20 @@ The FastAPI Project Template is licensed under the terms of the MIT license. The documentation below is for **your own project**, not the Project Template. 👇 -# FastAPI Project - -## Backend Requirements - -* [Docker](https://www.docker.com/). -* [Poetry](https://python-poetry.org/) for Python package and environment management. - -## Frontend Requirements - -* Node.js (with `npm`). - -## Backend local development - -* Start the stack with Docker Compose: - -```bash -docker compose up -d -``` - -* Now you can open your browser and interact with these URLs: - -Frontend, built with Docker, with routes handled based on the path: http://localhost - -Backend, JSON based web API based on OpenAPI: http://localhost/api/ - -Automatic interactive documentation with Swagger UI (from the OpenAPI backend): http://localhost/docs - -PGAdmin, PostgreSQL web administration: http://localhost:5050 - -Flower, administration of Celery tasks: http://localhost:5555 - -Traefik UI, to see how the routes are being handled by the proxy: http://localhost:8090 - -**Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it. - -To check the logs, run: - -```bash -docker compose logs -``` - -To check the logs of a specific service, add the name of the service, e.g.: - -```bash -docker compose logs backend -``` - -If your Docker is not running in `localhost` (the URLs above wouldn't work) you would need to use the IP or domain where your Docker is running. - -## Backend local development, additional details - -### General workflow - -By default, the dependencies are managed with [Poetry](https://python-poetry.org/), go there and install it. - -From `./backend/` you can install all the dependencies with: - -```console -$ poetry install -``` - -Then you can start a shell session with the new environment with: - -```console -$ poetry shell -``` - -Next, open your editor at `./backend/` (instead of the project root: `./`), so that you see an `./app/` directory with your code inside. That way, your editor will be able to find all the imports, etc. Make sure your editor uses the environment you just created with Poetry. - -Modify or add SQLModel models for data and SQL tables in `./backend/app/models.py`, API endpoints in `./backend/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/crud.py`. - -Add and modify tasks to the Celery worker in `./backend/app/worker.py`. - -### Docker Compose Override - -During development, you can change Docker Compose settings that will only affect the local development environment in the file `docker-compose.override.yml`. - -The changes to that file only affect the local development environment, not the production environment. So, you can add "temporary" changes that help the development workflow. - -For example, the directory with the backend code is mounted as a Docker "host volume", mapping the code you change live to the directory inside the container. That allows you to test your changes right away, without having to build the Docker image again. It should only be done during development, for production, you should build the Docker image with a recent version of the backend code. But during development, it allows you to iterate very fast. - -There is also a command override that runs `/start-reload.sh` (included in the base image) instead of the default `/start.sh` (also included in the base image). It starts a single server process (instead of multiple, as would be for production) and reloads the process whenever the code changes. Have in mind that if you have a syntax error and save the Python file, it will break and exit, and the container will stop. After that, you can restart the container by fixing the error and running again: - -```console -$ docker compose up -d -``` - -There is also a commented out `command` override, you can uncomment it and comment the default one. It makes the backend container run a process that does "nothing", but keeps the container alive. That allows you to get inside your running container and execute commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes. - -To get inside the container with a `bash` session you can start the stack with: - -```console -$ docker compose up -d -``` - -and then `exec` inside the running container: - -```console -$ docker compose exec backend bash -``` - -You should see an output like: - -```console -root@7f2607af31c3:/app# -``` - -that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory, this directory has another directory called "app" inside, that's where your code lives inside the container: `/app/app`. - -There you can use the script `/start-reload.sh` to run the debug live reloading server. You can run that script from inside the container with: - -```console -$ bash /start-reload.sh -``` - -...it will look like: - -```console -root@7f2607af31c3:/app# bash /start-reload.sh -``` - -and then hit enter. That runs the live reloading server that auto reloads when it detects code changes. - -Nevertheless, if it doesn't detect a change but a syntax error, it will just stop with an error. But as the container is still alive and you are in a Bash session, you can quickly restart it after fixing the error, running the same command ("up arrow" and "Enter"). - -...this previous detail is what makes it useful to have the container alive doing nothing and then, in a Bash session, make it run the live reload server. - -### Backend tests - -To test the backend run: - -```console -$ bash ./scripts/test.sh -``` - -The tests run with Pytest, modify and add tests to `./backend/app/tests/`. - -If you use GitHub Actions the tests will run automatically. - -#### Test running stack - -If your stack is already up and you just want to run the tests, you can use: - -```bash -docker compose exec backend /app/tests-start.sh -``` - -That `/app/tests-start.sh` script just calls `pytest` after making sure that the rest of the stack is running. If you need to pass extra arguments to `pytest`, you can pass them to that command and they will be forwarded. - -For example, to stop on first error: - -```bash -docker compose exec backend bash /app/tests-start.sh -x -``` - -#### Test Coverage - -Because the test scripts forward arguments to `pytest`, you can enable test coverage HTML report generation by passing `--cov-report=html`. - -To run the local tests with coverage HTML reports: - -```Bash -DOMAIN=backend sh ./scripts/test-local.sh --cov-report=html -``` - -To run the tests in a running stack with coverage HTML reports: - -```bash -docker compose exec backend bash /app/tests-start.sh --cov-report=html -``` - -### Migrations - -As during local development your app directory is mounted as a volume inside the container, you can also run the migrations with `alembic` commands inside the container and the migration code will be in your app directory (instead of being only inside the container). So you can add it to your git repository. - -Make sure you create a "revision" of your models and that you "upgrade" your database with that revision every time you change them. As this is what will update the tables in your database. Otherwise, your application will have errors. - -* Start an interactive session in the backend container: - -```console -$ docker compose exec backend bash -``` - -* Alembic is already configured to import your SQLModel models from `./backend/app/models.py`. - -* After changing a model (for example, adding a column), inside the container, create a revision, e.g.: - -```console -$ alembic revision --autogenerate -m "Add column last_name to User model" -``` - -* Commit to the git repository the files generated in the alembic directory. +## Backend Development -* After creating the revision, run the migration in the database (this is what will actually change the database): +See more instructions specific to backend development in [backend/README.md](./backend/README.md). -```console -$ alembic upgrade head -``` - -If you don't want to use migrations at all, uncomment the lines in the file at `./backend/app/db/init_db.py` that end in: - -```python -SQLModel.metadata.create_all(engine) -``` - -and comment the line in the file `prestart.sh` that contains: - -```console -$ alembic upgrade head -``` - -If you don't want to start with the default models and want to remove them / modify them, from the beginning, without having any previous revision, you can remove the revision files (`.py` Python files) under `./backend/app/alembic/versions/`. And then create a first migration as described above. - -### Development in `localhost` with a custom domain - -You might want to use something different than `localhost` as the domain. For example, if you are having problems with cookies that need a subdomain, and Chrome is not allowing you to use `localhost`. - -In that case, you have two options: you could use the instructions to modify your system `hosts` file with the instructions below in **Development with a custom IP** or you can just use `localhost.tiangolo.com`, it is set up to point to `localhost` (to the IP `127.0.0.1`) and all its subdomains too. And as it is an actual domain, the browsers will store the cookies you set during development, etc. - -If you used the default CORS enabled domains while generating the project, `localhost.tiangolo.com` was configured to be allowed. If you didn't, you will need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. - -To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `localhost.tiangolo.com`. - -After performing those steps you should be able to open: http://localhost.tiangolo.com and it will be served by your stack in `localhost`. - -Check all the corresponding available URLs in the section at the end. - -### Development with a custom IP - -If you are running Docker in an IP address different than `127.0.0.1` (`localhost`), you will need to perform some additional steps. That will be the case if you are running a custom Virtual Machine or your Docker is located in a different machine in your network. - -In that case, you will need to use a fake local domain (`dev.example.com`) and make your computer think that the domain is is served by the custom IP (e.g. `192.168.99.150`). - -If you have a custom domain like that, you need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. - -* Open your `hosts` file with administrative privileges using a text editor: - * **Note for Windows**: If you are in Windows, open the main Windows menu, search for "notepad", right click on it, and select the option "open as Administrator" or similar. Then click the "File" menu, "Open file", go to the directory `c:\Windows\System32\Drivers\etc\`, select the option to show "All files" instead of only "Text (.txt) files", and open the `hosts` file. - * **Note for Mac and Linux**: Your `hosts` file is probably located at `/etc/hosts`, you can edit it in a terminal running `sudo nano /etc/hosts`. - -* Additional to the contents it might have, add a new line with the custom IP (e.g. `192.168.99.150`) a space character, and your fake local domain: `dev.example.com`. - -The new line might look like: - -``` -192.168.99.150 dev.example.com -``` - -* Save the file. - * **Note for Windows**: Make sure you save the file as "All files", without an extension of `.txt`. By default, Windows tries to add the extension. Make sure the file is saved as is, without extension. - -...that will make your computer think that the fake local domain is served by that custom IP, and when you open that URL in your browser, it will talk directly to your locally running server when it is asked to go to `dev.example.com` and think that it is a remote server while it is actually running in your computer. - -To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `dev.example.com`. - -After performing those steps you should be able to open: http://dev.example.com and it will be server by your stack in `192.168.99.150`. - -Check all the corresponding available URLs in the section at the end. - -### Change the development "domain" - -If you need to use your local stack with a different domain than `localhost`, you need to make sure the domain you use points to the IP where your stack is set up. - -To simplify your Docker Compose setup, for example, so that the API docs (Swagger UI) knows where is your API, you should let it know you are using that domain for development. - -* Open the file located at `./.env`. It would have a line like: - -``` -DOMAIN=localhost -``` - -* Change it to the domain you are going to use, e.g.: - -``` -DOMAIN=localhost.tiangolo.com -``` - -That variable will be used by the Docker Compose files. - -After that, you can restart your stack with: - -```bash -docker compose up -d -``` - -and check all the corresponding available URLs in the section at the end. - -## Frontend development - -* Enter the `frontend` directory, install the NPM packages and start the live server using the `npm` scripts: - -```bash -cd frontend -npm install -npm run dev -``` +## Frontend Development -Then open your browser at http://localhost:5173/. - -Notice that this live server is not running inside Docker, it is for local development, and that is the recommended workflow. Once you are happy with your frontend, you can build the frontend Docker image and start it, to test it in a production-like environment. But compiling the image at every change will not be as productive as running the local development server with live reload. - -Check the file `package.json` to see other available options. - -### Removing the frontend - -If you are developing an API-only app and want to remove the frontend, you can do it easily: - -* Remove the `./frontend` directory. -* In the `docker-compose.yml` file, remove the whole service / section `frontend`. -* In the `docker-compose.override.yml` file, remove the whole service / section `frontend`. - -Done, you have a frontend-less (api-only) app. 🤓 - ---- - -If you want, you can also remove the `FRONTEND` environment variables from: - -* `.env` -* `./scripts/*.sh` - -But it would be only to clean them up, leaving them won't really have any effect either way. +See more instructions specific to frontend development in [frontend/README.md](./frontend/README.md). ## Deployment -You can deploy the using Docker Compose with a main Traefik proxy outside handling communication to the outside world and HTTPS certificates. - -And you can use CI (continuous integration) systems to do it automatically. - -But you have to configure a couple things first. - -### Traefik network - -This stack expects the public Traefik network to be named `traefik-public`. - -If you need to use a different Traefik public network name, update it in the `docker-compose.yml` files, in the section: - -```YAML -networks: - traefik-public: - external: true -``` - -Change `traefik-public` to the name of the used Traefik network. And then update it in the file `.env`: - -```bash -TRAEFIK_PUBLIC_NETWORK=traefik-public -``` - -## Docker Compose files and env vars - -There is a main `docker-compose.yml` file with all the configurations that apply to the whole stack, it is used automatically by `docker compose`. - -And there's also a `docker-compose.override.yml` with overrides for development, for example to mount the source code as a volume. It is used automatically by `docker compose` to apply overrides on top of `docker-compose.yml`. - -These Docker Compose files use the `.env` file containing configurations to be injected as environment variables in the containers. - -They also use some additional configurations taken from environment variables set in the scripts before calling the `docker compose` command. - -It is all designed to support several "stages", like development, building, testing, and deployment. Also, allowing the deployment to different environments like staging and production (and you can add more environments very easily). - -They are designed to have the minimum repetition of code and configurations, so that if you need to change something, you have to change it in the minimum amount of places. That's why files use environment variables that get auto-expanded. That way, if for example, you want to use a different domain, you can call the `docker compose` command with a different `DOMAIN` environment variable instead of having to change the domain in several places inside the Docker Compose files. - -Also, if you want to have another deployment environment, say `preprod`, you just have to change environment variables, but you can keep using the same Docker Compose files. - -### The .env file - -The `.env` file is the one that contains all your configurations, generated keys and passwords, etc. - -Depending on your workflow, you could want to exclude it from Git, for example if your project is public. In that case, you would have to make sure to set up a way for your CI tools to obtain it while building or deploying your project. - -One way to do it could be to add each environment variable to your CI/CD system, and updating the `docker-compose.yml` file to read that specific env var instead of reading the `.env` file. - -## URLs - -The production or staging URLs would use these same paths, but with your own domain. - -### Development URLs - -Development URLs, for local development. - -Frontend: http://localhost - -Backend: http://localhost/api/ - -Automatic Interactive Docs (Swagger UI): https://localhost/docs - -Automatic Alternative Docs (ReDoc): https://localhost/redoc - -PGAdmin: http://localhost:5050 - -Flower: http://localhost:5555 - -Traefik UI: http://localhost:8090 - -### Development in localhost with a custom domain URLs - -Development URLs, for local development. - -Frontend: http://localhost.tiangolo.com - -Backend: http://localhost.tiangolo.com/api/ - -Automatic Interactive Docs (Swagger UI): https://localhost.tiangolo.com/docs - -Automatic Alternative Docs (ReDoc): https://localhost.tiangolo.com/redoc +See more instructions specific to deployment in [deployment.md](./deployment.md). -PGAdmin: http://localhost.tiangolo.com:5050 +## Development -Flower: http://localhost.tiangolo.com:5555 +See general development instructions in [development.md](./development.md). -Traefik UI: http://localhost.tiangolo.com:8090 +This includes using Docker Compose, custom local domains, `.env` configurations, etc. diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000000..2c86bdad28 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,208 @@ +# FastAPI Project - Backend + +## Requirements + +* [Docker](https://www.docker.com/). +* [Poetry](https://python-poetry.org/) for Python package and environment management. + +## Local Development + +* Start the stack with Docker Compose: + +```bash +docker compose up -d +``` + +* Now you can open your browser and interact with these URLs: + +Frontend, built with Docker, with routes handled based on the path: http://localhost + +Backend, JSON based web API based on OpenAPI: http://localhost/api/ + +Automatic interactive documentation with Swagger UI (from the OpenAPI backend): http://localhost/docs + +PGAdmin, PostgreSQL web administration: http://localhost:5050 + +Flower, administration of Celery tasks: http://localhost:5555 + +Traefik UI, to see how the routes are being handled by the proxy: http://localhost:8090 + +**Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it. + +To check the logs, run: + +```bash +docker compose logs +``` + +To check the logs of a specific service, add the name of the service, e.g.: + +```bash +docker compose logs backend +``` + +If your Docker is not running in `localhost` (the URLs above wouldn't work) you would need to use the IP or domain where your Docker is running. + +## Backend local development, additional details + +### General workflow + +By default, the dependencies are managed with [Poetry](https://python-poetry.org/), go there and install it. + +From `./backend/` you can install all the dependencies with: + +```console +$ poetry install +``` + +Then you can start a shell session with the new environment with: + +```console +$ poetry shell +``` + +Make sure your editor is using the correct Python virtual environment. + +Modify or add SQLModel models for data and SQL tables in `./backend/app/models.py`, API endpoints in `./backend/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/crud.py`. + +Add and modify tasks to the Celery worker in `./backend/app/worker.py`. + +### Docker Compose Override + +During development, you can change Docker Compose settings that will only affect the local development environment in the file `docker-compose.override.yml`. + +The changes to that file only affect the local development environment, not the production environment. So, you can add "temporary" changes that help the development workflow. + +For example, the directory with the backend code is mounted as a Docker "host volume", mapping the code you change live to the directory inside the container. That allows you to test your changes right away, without having to build the Docker image again. It should only be done during development, for production, you should build the Docker image with a recent version of the backend code. But during development, it allows you to iterate very fast. + +There is also a command override that runs `/start-reload.sh` (included in the base image) instead of the default `/start.sh` (also included in the base image). It starts a single server process (instead of multiple, as would be for production) and reloads the process whenever the code changes. Have in mind that if you have a syntax error and save the Python file, it will break and exit, and the container will stop. After that, you can restart the container by fixing the error and running again: + +```console +$ docker compose up -d +``` + +There is also a commented out `command` override, you can uncomment it and comment the default one. It makes the backend container run a process that does "nothing", but keeps the container alive. That allows you to get inside your running container and execute commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes. + +To get inside the container with a `bash` session you can start the stack with: + +```console +$ docker compose up -d +``` + +and then `exec` inside the running container: + +```console +$ docker compose exec backend bash +``` + +You should see an output like: + +```console +root@7f2607af31c3:/app# +``` + +that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory, this directory has another directory called "app" inside, that's where your code lives inside the container: `/app/app`. + +There you can use the script `/start-reload.sh` to run the debug live reloading server. You can run that script from inside the container with: + +```console +$ bash /start-reload.sh +``` + +...it will look like: + +```console +root@7f2607af31c3:/app# bash /start-reload.sh +``` + +and then hit enter. That runs the live reloading server that auto reloads when it detects code changes. + +Nevertheless, if it doesn't detect a change but a syntax error, it will just stop with an error. But as the container is still alive and you are in a Bash session, you can quickly restart it after fixing the error, running the same command ("up arrow" and "Enter"). + +...this previous detail is what makes it useful to have the container alive doing nothing and then, in a Bash session, make it run the live reload server. + +### Backend tests + +To test the backend run: + +```console +$ bash ./scripts/test.sh +``` + +The tests run with Pytest, modify and add tests to `./backend/app/tests/`. + +If you use GitHub Actions the tests will run automatically. + +#### Test running stack + +If your stack is already up and you just want to run the tests, you can use: + +```bash +docker compose exec backend /app/tests-start.sh +``` + +That `/app/tests-start.sh` script just calls `pytest` after making sure that the rest of the stack is running. If you need to pass extra arguments to `pytest`, you can pass them to that command and they will be forwarded. + +For example, to stop on first error: + +```bash +docker compose exec backend bash /app/tests-start.sh -x +``` + +#### Test Coverage + +Because the test scripts forward arguments to `pytest`, you can enable test coverage HTML report generation by passing `--cov-report=html`. + +To run the local tests with coverage HTML reports: + +```Bash +DOMAIN=backend sh ./scripts/test-local.sh --cov-report=html +``` + +To run the tests in a running stack with coverage HTML reports: + +```bash +docker compose exec backend bash /app/tests-start.sh --cov-report=html +``` + +### Migrations + +As during local development your app directory is mounted as a volume inside the container, you can also run the migrations with `alembic` commands inside the container and the migration code will be in your app directory (instead of being only inside the container). So you can add it to your git repository. + +Make sure you create a "revision" of your models and that you "upgrade" your database with that revision every time you change them. As this is what will update the tables in your database. Otherwise, your application will have errors. + +* Start an interactive session in the backend container: + +```console +$ docker compose exec backend bash +``` + +* Alembic is already configured to import your SQLModel models from `./backend/app/models.py`. + +* After changing a model (for example, adding a column), inside the container, create a revision, e.g.: + +```console +$ alembic revision --autogenerate -m "Add column last_name to User model" +``` + +* Commit to the git repository the files generated in the alembic directory. + +* After creating the revision, run the migration in the database (this is what will actually change the database): + +```console +$ alembic upgrade head +``` + +If you don't want to use migrations at all, uncomment the lines in the file at `./backend/app/db/init_db.py` that end in: + +```python +SQLModel.metadata.create_all(engine) +``` + +and comment the line in the file `prestart.sh` that contains: + +```console +$ alembic upgrade head +``` + +If you don't want to start with the default models and want to remove them / modify them, from the beginning, without having any previous revision, you can remove the revision files (`.py` Python files) under `./backend/app/alembic/versions/`. And then create a first migration as described above. diff --git a/deployment.md b/deployment.md new file mode 100644 index 0000000000..f761b3687e --- /dev/null +++ b/deployment.md @@ -0,0 +1,25 @@ +# FastAPI Project - Deployment + +You can deploy the using Docker Compose with a main Traefik proxy outside handling communication to the outside world and HTTPS certificates. + +And you can use CI (continuous integration) systems to do it automatically. + +But you have to configure a couple things first. + +## Traefik network + +This stack expects the public Traefik network to be named `traefik-public`. + +If you need to use a different Traefik public network name, update it in the `docker-compose.yml` files, in the section: + +```YAML +networks: + traefik-public: + external: true +``` + +Change `traefik-public` to the name of the used Traefik network. And then update it in the file `.env`: + +```bash +TRAEFIK_PUBLIC_NETWORK=traefik-public +``` diff --git a/development.md b/development.md new file mode 100644 index 0000000000..368c286b24 --- /dev/null +++ b/development.md @@ -0,0 +1,132 @@ +# FastAPI Project - Development + +## Development in `localhost` with a custom domain + +You might want to use something different than `localhost` as the domain. For example, if you are having problems with cookies that need a subdomain, and Chrome is not allowing you to use `localhost`. + +In that case, you have two options: you could use the instructions to modify your system `hosts` file with the instructions below in **Development with a custom IP** or you can just use `localhost.tiangolo.com`, it is set up to point to `localhost` (to the IP `127.0.0.1`) and all its subdomains too. And as it is an actual domain, the browsers will store the cookies you set during development, etc. + +If you used the default CORS enabled domains while generating the project, `localhost.tiangolo.com` was configured to be allowed. If you didn't, you will need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. + +To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `localhost.tiangolo.com`. + +After performing those steps you should be able to open: http://localhost.tiangolo.com and it will be served by your stack in `localhost`. + +Check all the corresponding available URLs in the section at the end. + +## Development with a custom IP + +If you are running Docker in an IP address different than `127.0.0.1` (`localhost`), you will need to perform some additional steps. That will be the case if you are running a custom Virtual Machine or your Docker is located in a different machine in your network. + +In that case, you will need to use a fake local domain (`dev.example.com`) and make your computer think that the domain is is served by the custom IP (e.g. `192.168.99.150`). + +If you have a custom domain like that, you need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. + +* Open your `hosts` file with administrative privileges using a text editor: + * **Note for Windows**: If you are in Windows, open the main Windows menu, search for "notepad", right click on it, and select the option "open as Administrator" or similar. Then click the "File" menu, "Open file", go to the directory `c:\Windows\System32\Drivers\etc\`, select the option to show "All files" instead of only "Text (.txt) files", and open the `hosts` file. + * **Note for Mac and Linux**: Your `hosts` file is probably located at `/etc/hosts`, you can edit it in a terminal running `sudo nano /etc/hosts`. + +* Additional to the contents it might have, add a new line with the custom IP (e.g. `192.168.99.150`) a space character, and your fake local domain: `dev.example.com`. + +The new line might look like: + +``` +192.168.99.150 dev.example.com +``` + +* Save the file. + * **Note for Windows**: Make sure you save the file as "All files", without an extension of `.txt`. By default, Windows tries to add the extension. Make sure the file is saved as is, without extension. + +...that will make your computer think that the fake local domain is served by that custom IP, and when you open that URL in your browser, it will talk directly to your locally running server when it is asked to go to `dev.example.com` and think that it is a remote server while it is actually running in your computer. + +To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `dev.example.com`. + +After performing those steps you should be able to open: http://dev.example.com and it will be server by your stack in `192.168.99.150`. + +Check all the corresponding available URLs in the section at the end. + +## Change the development "domain" + +If you need to use your local stack with a different domain than `localhost`, you need to make sure the domain you use points to the IP where your stack is set up. + +To simplify your Docker Compose setup, for example, so that the API docs (Swagger UI) knows where is your API, you should let it know you are using that domain for development. + +* Open the file located at `./.env`. It would have a line like: + +``` +DOMAIN=localhost +``` + +* Change it to the domain you are going to use, e.g.: + +``` +DOMAIN=localhost.tiangolo.com +``` + +That variable will be used by the Docker Compose files. + +After that, you can restart your stack with: + +```bash +docker compose up -d +``` + +and check all the corresponding available URLs in the section at the end. + +## Docker Compose files and env vars + +There is a main `docker-compose.yml` file with all the configurations that apply to the whole stack, it is used automatically by `docker compose`. + +And there's also a `docker-compose.override.yml` with overrides for development, for example to mount the source code as a volume. It is used automatically by `docker compose` to apply overrides on top of `docker-compose.yml`. + +These Docker Compose files use the `.env` file containing configurations to be injected as environment variables in the containers. + +They also use some additional configurations taken from environment variables set in the scripts before calling the `docker compose` command. + +## The .env file + +The `.env` file is the one that contains all your configurations, generated keys and passwords, etc. + +Depending on your workflow, you could want to exclude it from Git, for example if your project is public. In that case, you would have to make sure to set up a way for your CI tools to obtain it while building or deploying your project. + +One way to do it could be to add each environment variable to your CI/CD system, and updating the `docker-compose.yml` file to read that specific env var instead of reading the `.env` file. + +## URLs + +The production or staging URLs would use these same paths, but with your own domain. + +### Development URLs + +Development URLs, for local development. + +Frontend: http://localhost + +Backend: http://localhost/api/ + +Automatic Interactive Docs (Swagger UI): https://localhost/docs + +Automatic Alternative Docs (ReDoc): https://localhost/redoc + +PGAdmin: http://localhost:5050 + +Flower: http://localhost:5555 + +Traefik UI: http://localhost:8090 + +### Development in localhost with a custom domain URLs + +Development URLs, for local development. + +Frontend: http://localhost.tiangolo.com + +Backend: http://localhost.tiangolo.com/api/ + +Automatic Interactive Docs (Swagger UI): https://localhost.tiangolo.com/docs + +Automatic Alternative Docs (ReDoc): https://localhost.tiangolo.com/redoc + +PGAdmin: http://localhost.tiangolo.com:5050 + +Flower: http://localhost.tiangolo.com:5555 + +Traefik UI: http://localhost.tiangolo.com:8090 diff --git a/new-frontend/README.md b/new-frontend/README.md index f22e2e432f..21e2bf91eb 100644 --- a/new-frontend/README.md +++ b/new-frontend/README.md @@ -1,16 +1,51 @@ -# Full Stack FastAPI and PostgreSQL - Frontend +# FastAPI Project - Frontend + +## Frontend development + +* Enter the `frontend` directory, install the NPM packages and start the live server using the `npm` scripts: + +```bash +cd frontend +npm install +npm run dev +``` + +Then open your browser at http://localhost:5173/. + +Notice that this live server is not running inside Docker, it is for local development, and that is the recommended workflow. Once you are happy with your frontend, you can build the frontend Docker image and start it, to test it in a production-like environment. But compiling the image at every change will not be as productive as running the local development server with live reload. + +Check the file `package.json` to see other available options. + +### Removing the frontend + +If you are developing an API-only app and want to remove the frontend, you can do it easily: + +* Remove the `./frontend` directory. +* In the `docker-compose.yml` file, remove the whole service / section `frontend`. +* In the `docker-compose.override.yml` file, remove the whole service / section `frontend`. + +Done, you have a frontend-less (api-only) app. 🤓 + +--- + +If you want, you can also remove the `FRONTEND` environment variables from: + +* `.env` +* `./scripts/*.sh` + +But it would be only to clean them up, leaving them won't really have any effect either way. ## Generate Client -- Start the Docker Compose stack. -- Download the OpenAPI JSON file from `http://localhost/api/v1/openapi.json` and copy it to a new file `openapi.json` next to the `package.json` file. -- To simplify the names in the generated frontend client code, modifying the `openapi.json` file, run: +* Start the Docker Compose stack. +* Download the OpenAPI JSON file from `http://localhost/api/v1/openapi.json` and copy it to a new file `openapi.json` next to the `package.json` file. +* To simplify the names in the generated frontend client code, modifying the `openapi.json` file, run: ```bash node modify-openapi-operationids.js ``` -- To generate or update the frontend client, run: +* To generate or update the frontend client, run: ```bash npm run generate-client diff --git a/scripts/test.sh b/scripts/test.sh index ef63b5e4d0..8e69e23158 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -3,16 +3,8 @@ # Exit in case of error set -e -DOMAIN=backend \ -SMTP_HOST="" \ -TRAEFIK_PUBLIC_NETWORK_IS_EXTERNAL=false \ -INSTALL_DEV=true \ -docker-compose \ --f docker-compose.yml \ -config > docker-stack.yml - -docker-compose -f docker-stack.yml build -docker-compose -f docker-stack.yml down -v --remove-orphans # Remove possibly previous broken stacks left hanging after an error -docker-compose -f docker-stack.yml up -d -docker-compose -f docker-stack.yml exec -T backend bash /app/tests-start.sh "$@" -docker-compose -f docker-stack.yml down -v --remove-orphans +docker compose build +docker compose down -v --remove-orphans # Remove possibly previous broken stacks left hanging after an error +docker compose up -d +docker compose exec -T backend bash /app/tests-start.sh "$@" +docker compose down -v --remove-orphans From 6541476a383c808f519722552b765292d9d47a16 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Mar 2024 19:50:03 +0000 Subject: [PATCH 235/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index c5eea89cd0..d0d8b5eece 100644 --- a/release-notes.md +++ b/release-notes.md @@ -90,6 +90,7 @@ ### Docs +* 📝 Refactor README into separate README.md files for backend, frontend, deployment, development. PR [#639](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/639) by [@tiangolo](https://github.com/tiangolo). * 📝 Update README. PR [#628](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/628) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action latest-changes and move release notes to independent file. PR [#619](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/619) by [@tiangolo](https://github.com/tiangolo). * 📝 Update internal README and referred files. PR [#613](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/613) by [@tiangolo](https://github.com/tiangolo). From 945eb8dc5c05dcf95f8ddc9377bfda67c226ddd3 Mon Sep 17 00:00:00 2001 From: Esteban Maya Date: Thu, 7 Mar 2024 18:03:15 -0500 Subject: [PATCH 236/771] =?UTF-8?q?=F0=9F=91=B7=20Add=20coverage=20with=20?= =?UTF-8?q?Smokeshow=20to=20CI=20and=20badge=20(#638)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .github/workflows/smokeshow.yml | 35 +++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 19 +++++++++++++++++- README.md | 8 ++++++++ backend/scripts/test.sh | 4 +++- 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/smokeshow.yml diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml new file mode 100644 index 0000000000..b685a1c680 --- /dev/null +++ b/.github/workflows/smokeshow.yml @@ -0,0 +1,35 @@ +name: Smokeshow + +on: + workflow_run: + workflows: [Test] + types: [completed] + +permissions: + statuses: write + +jobs: + smokeshow: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - run: pip install smokeshow + + - uses: dawidd6/action-download-artifact@v2.28.0 + with: + workflow: test.yml + commit: ${{ github.event.workflow_run.head_sha }} + + - run: smokeshow upload coverage-html + env: + SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} + SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 90 + SMOKESHOW_GITHUB_CONTEXT: coverage + SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index febd40de88..1f25ec92d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,23 @@ jobs: - name: Docker Compose up run: docker compose up -d - name: Docker Compose run tests - run: docker compose exec -T backend bash /app/tests-start.sh + run: docker compose exec -T backend bash /app/tests-start.sh "Coverage for ${{ github.sha }}" - name: Docker Compose cleanup run: docker compose down -v --remove-orphans + - name: Store coverage files + uses: actions/upload-artifact@v3 + with: + name: coverage-html + path: backend/htmlcov + + # https://github.com/marketplace/actions/alls-green#why + alls-green: # This job does nothing and is only used for the branch protection + if: always() + needs: + - test + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/README.md b/README.md index ae468dac79..7b8eeb9b35 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +

+ + Test + + + Coverage +

+ # FastAPI Project Template ## 🚨 Warning: in (re) construction 😎 🏗️ diff --git a/backend/scripts/test.sh b/backend/scripts/test.sh index fba8e95576..afc004c8cd 100755 --- a/backend/scripts/test.sh +++ b/backend/scripts/test.sh @@ -3,4 +3,6 @@ set -e set -x -pytest --cov=app --cov-report=term-missing app/tests "${@}" +coverage run --source=app -m pytest +coverage report --show-missing +coverage html --title "${@}" From cd3982e1a31dadf1e4d719a3d6bf9e079457d3f4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Mar 2024 23:03:32 +0000 Subject: [PATCH 237/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index d0d8b5eece..df93e4cfe5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -11,6 +11,7 @@ ### Features +* 👷 Add coverage with Smokeshow to CI and badge. PR [#638](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/638) by [@estebanx64](https://github.com/estebanx64). * ✨ Migrate to TanStack Query (React Query) and TanStack Router. PR [#637](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/637) by [@alejsdev](https://github.com/alejsdev). * ✅ Add setup and teardown database for tests. PR [#626](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/626) by [@estebanx64](https://github.com/estebanx64). * ✨ Update new-frontend client. PR [#625](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/625) by [@alejsdev](https://github.com/alejsdev). From 14b1866cd0e0dd7170e595ea19d79928ba65e862 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 00:08:07 +0100 Subject: [PATCH 238/771] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Add=20Prettier=20a?= =?UTF-8?q?nd=20ESLint=20config=20with=20pre-commit=20(#640)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 51 +- new-frontend/.eslintignore | 1 + new-frontend/.eslintrc.cjs | 18 - new-frontend/.eslintrc.json | 13 + new-frontend/.prettierignore | 1 + new-frontend/.prettierrc | 10 + new-frontend/package-lock.json | 3745 ++++++++++++++++++++++++++++++-- new-frontend/package.json | 12 +- 8 files changed, 3601 insertions(+), 250 deletions(-) create mode 100644 new-frontend/.eslintignore delete mode 100644 new-frontend/.eslintrc.cjs create mode 100644 new-frontend/.eslintrc.json create mode 100644 new-frontend/.prettierignore create mode 100644 new-frontend/.prettierrc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 984c6caa0a..4d5a12745a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,25 +1,46 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -default_language_version: - python: python3.10 repos: -- repo: https://github.com/pre-commit/pre-commit-hooks + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: check-added-large-files - - id: check-toml - - id: check-yaml + - id: check-added-large-files + - id: check-toml + - id: check-yaml args: - - --unsafe - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/charliermarsh/ruff-pre-commit + - --unsafe + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/charliermarsh/ruff-pre-commit rev: v0.2.2 hooks: - - id: ruff + - id: ruff args: - - --fix - - id: ruff-format + - --fix + - id: ruff-format + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.1.0 + hooks: + - id: prettier + additional_dependencies: + - prettier@3.2.5 + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.56.0 + hooks: + - id: eslint + files: \.(js|jsx|ts|tsx)$ + additional_dependencies: + - eslint@8.57.0 + - eslint-config-standard-with-typescript@43.0.1 + - eslint-plugin-import@2.29.1 + - eslint-plugin-n@16.6.2 + - eslint-plugin-promise@6.1.1 + - eslint-plugin-react@7.34.0 + - eslint-plugin-react-hooks@4.6.0 + - eslint-plugin-react-refresh@0.4.4 + - "@typescript-eslint/eslint-plugin@6.21.0" + - "@typescript-eslint/parser@6.10.0" + ci: - autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks - autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate + autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks + autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate diff --git a/new-frontend/.eslintignore b/new-frontend/.eslintignore new file mode 100644 index 0000000000..c950e9ce1c --- /dev/null +++ b/new-frontend/.eslintignore @@ -0,0 +1 @@ +new-frontend/src/client/** diff --git a/new-frontend/.eslintrc.cjs b/new-frontend/.eslintrc.cjs deleted file mode 100644 index d6c9537953..0000000000 --- a/new-frontend/.eslintrc.cjs +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} diff --git a/new-frontend/.eslintrc.json b/new-frontend/.eslintrc.json new file mode 100644 index 0000000000..cfd443bb31 --- /dev/null +++ b/new-frontend/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": ["standard-with-typescript", "plugin:react/recommended"], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["react"], + "rules": {} +} diff --git a/new-frontend/.prettierignore b/new-frontend/.prettierignore new file mode 100644 index 0000000000..c950e9ce1c --- /dev/null +++ b/new-frontend/.prettierignore @@ -0,0 +1 @@ +new-frontend/src/client/** diff --git a/new-frontend/.prettierrc b/new-frontend/.prettierrc new file mode 100644 index 0000000000..c4635f89c8 --- /dev/null +++ b/new-frontend/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 80, + "tabWidth": 2, + "importOrder": ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true +} diff --git a/new-frontend/package-lock.json b/new-frontend/package-lock.json index 13f87612bf..05a30618de 100644 --- a/new-frontend/package-lock.json +++ b/new-frontend/package-lock.json @@ -29,14 +29,20 @@ "@types/node": "20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", - "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/parser": "^6.10.0", "@vitejs/plugin-react-swc": "^3.5.0", - "eslint": "^8.53.0", + "eslint": "8.57.0", + "eslint-config-standard-with-typescript": "43.0.1", + "eslint-plugin-import": "2.29.1", + "eslint-plugin-n": "16.6.2", + "eslint-plugin-promise": "6.1.1", + "eslint-plugin-react": "7.34.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.4", "openapi-typescript-codegen": "0.25.0", - "typescript": "^5.2.2", + "prettier": "3.2.5", + "typescript": "5.4.2", "vite": "^5.0.0" } }, @@ -253,9 +259,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -1955,9 +1961,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "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", @@ -1977,45 +1983,23 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/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/@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "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/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "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.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2036,9 +2020,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@jsdevtools/ono": { @@ -2562,6 +2546,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/lodash": { "version": "4.14.202", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", @@ -2622,22 +2612,22 @@ "devOptional": true }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", - "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2662,6 +2652,53 @@ } } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/parser": { "version": "6.13.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", @@ -2708,13 +2745,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", - "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2734,6 +2771,88 @@ } } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/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/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/types": { "version": "6.13.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", @@ -2775,17 +2894,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", - "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -2799,6 +2918,105 @@ "eslint": "^7.0.0 || ^8.0.0" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/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/@typescript-eslint/utils/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "6.13.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", @@ -2853,9 +3071,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "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" @@ -2873,6 +3091,22 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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", @@ -2914,6 +3148,41 @@ "node": ">=10" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2923,33 +3192,197 @@ "node": ">=8" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", + "dev": true, "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "node_modules/array.prototype.findlast": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz", + "integrity": "sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==", + "dev": true, "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=10", - "npm": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" } }, "node_modules/balanced-match": { @@ -3001,6 +3434,46 @@ "unload": "2.2.0" } }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -3199,6 +3672,40 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3249,6 +3756,154 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", + "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", + "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.4", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", @@ -3298,16 +3953,16 @@ } }, "node_modules/eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "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.3", - "@eslint/js": "8.54.0", - "@humanwhocodes/config-array": "^0.11.13", + "@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", @@ -3337,19 +3992,277 @@ "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" + "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-compat-utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", + "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "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" + } + ], + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-config-standard-with-typescript": { + "version": "43.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-43.0.1.tgz", + "integrity": "sha512-WfZ986+qzIzX6dcr4yGUyVb/l9N3Z8wPXCc5z/70fljs3UbWhhV+WxrfgsqMToRzuuyX9MqZ974pq2UPhDTOcA==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^6.4.0", + "eslint-config-standard": "17.1.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.4.0", + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0", + "typescript": "*" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", + "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.6.0", + "eslint-compat-utils": "^0.1.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-n": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" }, - "bin": { - "eslint": "bin/eslint.js" + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.34.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz", + "integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlast": "^1.2.4", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.17", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7", + "object.hasown": "^1.1.3", + "object.values": "^1.1.7", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.10" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/eslint-plugin-react-hooks": { @@ -3373,6 +4286,44 @@ "eslint": ">=7" } }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -3401,28 +4352,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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/eslint/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/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -3632,6 +4561,15 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -3737,6 +4675,52 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -3745,6 +4729,35 @@ "node": ">=6" } }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3777,9 +4790,9 @@ } }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "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" @@ -3791,6 +4804,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -3820,6 +4848,18 @@ "csstype": "^3.0.10" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3853,6 +4893,15 @@ "uglify-js": "^3.1.4" } }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3862,6 +4911,57 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", @@ -3928,6 +5028,20 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -3936,11 +5050,97 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -3952,6 +5152,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3961,6 +5176,33 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3973,6 +5215,27 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3982,6 +5245,21 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -3991,12 +5269,150 @@ "node": ">=8" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "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/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -4043,12 +5459,30 @@ "node": ">=10" } }, + "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/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -4061,6 +5495,21 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4264,6 +5713,116 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", + "dev": true, + "dependencies": { + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/oblivious-set": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", @@ -4425,6 +5984,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -4703,11 +6271,50 @@ } } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", + "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0", + "get-intrinsic": "^1.2.3", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/remove-accents": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", @@ -4737,6 +6344,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4812,6 +6428,41 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -4835,6 +6486,38 @@ "node": ">=10" } }, + "node_modules/set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4856,6 +6539,24 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4883,6 +6584,71 @@ "node": ">=0.10.0" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4895,6 +6661,15 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4988,6 +6763,18 @@ "typescript": ">=4.2.0" } }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -5017,10 +6804,83 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5043,6 +6903,21 @@ "node": ">=0.8.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -5195,6 +7070,82 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -5436,9 +7387,9 @@ } }, "@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "requires": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -6630,9 +8581,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -6644,42 +8595,22 @@ "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "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 - } } }, "@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "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 }, "@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" } }, @@ -6690,9 +8621,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "@jsdevtools/ono": { @@ -6977,6 +8908,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "@types/lodash": { "version": "4.14.202", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", @@ -7037,28 +8974,56 @@ "devOptional": true }, "@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", - "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", "natural-compare": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + } + }, + "@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + } + } } }, "@typescript-eslint/parser": { @@ -7085,15 +9050,67 @@ } }, "@typescript-eslint/type-utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", - "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + } + }, + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "@typescript-eslint/types": { @@ -7118,18 +9135,80 @@ } }, "@typescript-eslint/utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", - "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + } + }, + "@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + } + }, + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "@typescript-eslint/visitor-keys": { @@ -7176,9 +9255,9 @@ } }, "acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true }, "acorn-jsx": { @@ -7188,6 +9267,18 @@ "dev": true, "requires": {} }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -7217,17 +9308,162 @@ "tslib": "^2.0.0" } }, + "array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + } + }, + "array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + } + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } + }, + "array.prototype.findlast": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz", + "integrity": "sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.tosorted": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + } + }, + "asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, "axios": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", @@ -7291,6 +9527,34 @@ "unload": "2.2.0" } }, + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, "call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -7441,6 +9705,28 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7482,6 +9768,130 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", + "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, + "es-iterator-helpers": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", + "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", + "dev": true, + "requires": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.4", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.0" + } + }, + "es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + } + }, + "es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "requires": { + "hasown": "^2.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "esbuild": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", @@ -7518,16 +9928,16 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", - "@humanwhocodes/config-array": "^0.11.13", + "@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", @@ -7561,24 +9971,212 @@ "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" + } + }, + "eslint-compat-utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", + "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "dev": true, + "requires": {} + }, + "eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "dev": true, + "requires": {} + }, + "eslint-config-standard-with-typescript": { + "version": "43.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-43.0.1.tgz", + "integrity": "sha512-WfZ986+qzIzX6dcr4yGUyVb/l9N3Z8wPXCc5z/70fljs3UbWhhV+WxrfgsqMToRzuuyX9MqZ974pq2UPhDTOcA==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^6.4.0", + "eslint-config-standard": "17.1.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-es-x": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", + "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.6.0", + "eslint-compat-utils": "^0.1.2" + } + }, + "eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "requires": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "eslint-plugin-n": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" + } + }, + "eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "requires": {} + }, + "eslint-plugin-react": { + "version": "7.34.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz", + "integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.7", + "array.prototype.findlast": "^1.2.4", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.17", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7", + "object.hasown": "^1.1.3", + "object.values": "^1.1.7", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.10" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, - "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==", + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -7768,6 +10366,15 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -7847,11 +10454,62 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, + "function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, "get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" }, + "get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + } + }, + "get-tsconfig": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -7875,14 +10533,23 @@ } }, "globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -7904,6 +10571,15 @@ "dev": true, "requires": {} }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -7929,12 +10605,48 @@ "wordwrap": "^1.0.0" } }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, "hasown": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", @@ -7986,6 +10698,17 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + } + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -7994,11 +10717,64 @@ "loose-envify": "^1.0.0" } }, + "is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, + "is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, "is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -8007,12 +10783,39 @@ "hasown": "^2.0.0" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, + "is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -8022,24 +10825,141 @@ "is-extglob": "^2.1.1" } }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "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 }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.14" + } + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "requires": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -8079,12 +10999,27 @@ "@apidevtools/json-schema-ref-parser": "9.0.9" } }, + "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 + }, "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 }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -8095,6 +11030,18 @@ "universalify": "^2.0.0" } }, + "jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + } + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8250,6 +11197,86 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "object.groupby": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", + "dev": true, + "requires": { + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" + } + }, + "object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "requires": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, "oblivious-set": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", @@ -8366,6 +11393,12 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true + }, "postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -8517,11 +11550,38 @@ "tslib": "^2.0.0" } }, + "reflect.getprototypeof": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", + "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0", + "get-intrinsic": "^1.2.3", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + } + }, "regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + } + }, "remove-accents": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", @@ -8542,6 +11602,12 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -8586,6 +11652,29 @@ "queue-microtask": "^1.2.2" } }, + "safe-array-concat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + } + }, + "safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + } + }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -8603,6 +11692,32 @@ "lru-cache": "^6.0.0" } }, + "set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dev": true, + "requires": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8618,6 +11733,18 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8636,6 +11763,56 @@ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, + "string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8645,6 +11822,12 @@ "ansi-regex": "^5.0.1" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -8712,6 +11895,18 @@ "dev": true, "requires": {} }, + "tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -8732,10 +11927,62 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + } + }, "typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true }, "uglify-js": { @@ -8745,6 +11992,18 @@ "dev": true, "optional": true }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -8819,6 +12078,64 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "requires": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.1" + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/new-frontend/package.json b/new-frontend/package.json index 11b2bd72d3..94777e2a05 100644 --- a/new-frontend/package.json +++ b/new-frontend/package.json @@ -32,14 +32,20 @@ "@types/node": "20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", - "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/parser": "^6.10.0", "@vitejs/plugin-react-swc": "^3.5.0", - "eslint": "^8.53.0", + "eslint": "8.57.0", + "eslint-config-standard-with-typescript": "43.0.1", + "eslint-plugin-import": "2.29.1", + "eslint-plugin-n": "16.6.2", + "eslint-plugin-promise": "6.1.1", + "eslint-plugin-react": "7.34.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.4", "openapi-typescript-codegen": "0.25.0", - "typescript": "^5.2.2", + "prettier": "3.2.5", + "typescript": "5.4.2", "vite": "^5.0.0" } } From 43b4434770e57ceccfc97098aa4991ef271f8b40 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Mar 2024 23:08:24 +0000 Subject: [PATCH 239/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index df93e4cfe5..e530a3293f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -11,6 +11,7 @@ ### Features +* ⚙️ Add Prettier and ESLint config with pre-commit. PR [#640](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/640) by [@alejsdev](https://github.com/alejsdev). * 👷 Add coverage with Smokeshow to CI and badge. PR [#638](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/638) by [@estebanx64](https://github.com/estebanx64). * ✨ Migrate to TanStack Query (React Query) and TanStack Router. PR [#637](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/637) by [@alejsdev](https://github.com/alejsdev). * ✅ Add setup and teardown database for tests. PR [#626](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/626) by [@estebanx64](https://github.com/estebanx64). From eecca1c1aad44b559abd3f485bcd35706b370dc2 Mon Sep 17 00:00:00 2001 From: Esteban Maya Date: Thu, 7 Mar 2024 18:21:46 -0500 Subject: [PATCH 240/771] =?UTF-8?q?=E2=9C=85=20Add=20tests=20to=20raise=20?= =?UTF-8?q?coverage=20to=20at=20least=2090%=20and=20fix=20recover=20passwo?= =?UTF-8?q?rd=20logic=20(#632)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- backend/app/api/routes/login.py | 8 +- backend/app/api/routes/users.py | 5 +- backend/app/backend_pre_start.py | 7 +- backend/app/celeryworker_pre_start.py | 7 +- backend/app/crud.py | 1 + backend/app/tests/api/api_v1/test_items.py | 124 ++++++++ backend/app/tests/api/api_v1/test_login.py | 70 +++++ backend/app/tests/api/api_v1/test_users.py | 264 +++++++++++++++++- backend/app/tests/scripts/__init__.py | 0 .../tests/scripts/test_backend_pre_start.py | 28 ++ .../tests/scripts/test_celery_pre_start.py | 28 ++ .../app/tests/scripts/test_test_pre_start.py | 28 ++ backend/app/tests_pre_start.py | 7 +- backend/pyproject.toml | 1 + 14 files changed, 561 insertions(+), 17 deletions(-) create mode 100644 backend/app/tests/scripts/__init__.py create mode 100644 backend/app/tests/scripts/test_backend_pre_start.py create mode 100644 backend/app/tests/scripts/test_celery_pre_start.py create mode 100644 backend/app/tests/scripts/test_test_pre_start.py diff --git a/backend/app/api/routes/login.py b/backend/app/api/routes/login.py index 4b741ce880..30831928f2 100644 --- a/backend/app/api/routes/login.py +++ b/backend/app/api/routes/login.py @@ -9,7 +9,7 @@ from app.core import security from app.core.config import settings from app.core.security import get_password_hash -from app.models import Message, NewPassword, Token, UserOut +from app.models import Message, NewPassword, Token, User, UserOut from app.utils import ( generate_password_reset_token, send_reset_password_email, @@ -73,10 +73,10 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message: """ Reset password """ - email = verify_password_reset_token(token=body.token) - if not email: + user_id = verify_password_reset_token(token=body.token) + if not user_id: raise HTTPException(status_code=400, detail="Invalid token") - user = crud.get_user_by_email(session=session, email=email) + user = session.get(User, int(user_id)) if not user: raise HTTPException( status_code=404, diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 1c76293aed..0850c929f6 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -144,8 +144,7 @@ def read_user_by_id( return user if not current_user.is_superuser: raise HTTPException( - # TODO: Review status code - status_code=400, + status_code=403, detail="The user doesn't have enough privileges", ) return user @@ -194,5 +193,5 @@ def delete_user( return Message(message="User deleted successfully") elif user == current_user and current_user.is_superuser: raise HTTPException( - status_code=400, detail="Super users are not allowed to delete themselves" + status_code=403, detail="Super users are not allowed to delete themselves" ) diff --git a/backend/app/backend_pre_start.py b/backend/app/backend_pre_start.py index 1693d257d8..c2f8e29ae1 100644 --- a/backend/app/backend_pre_start.py +++ b/backend/app/backend_pre_start.py @@ -1,5 +1,6 @@ import logging +from sqlalchemy import Engine from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed @@ -18,9 +19,9 @@ before=before_log(logger, logging.INFO), after=after_log(logger, logging.WARN), ) -def init() -> None: +def init(db_engine: Engine) -> None: try: - with Session(engine) as session: + with Session(db_engine) as session: # Try to create session to check if DB is awake session.exec(select(1)) except Exception as e: @@ -30,7 +31,7 @@ def init() -> None: def main() -> None: logger.info("Initializing service") - init() + init(engine) logger.info("Service finished initializing") diff --git a/backend/app/celeryworker_pre_start.py b/backend/app/celeryworker_pre_start.py index a9336023b3..0ce6045635 100644 --- a/backend/app/celeryworker_pre_start.py +++ b/backend/app/celeryworker_pre_start.py @@ -1,5 +1,6 @@ import logging +from sqlalchemy import Engine from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed @@ -18,10 +19,10 @@ before=before_log(logger, logging.INFO), after=after_log(logger, logging.WARN), ) -def init() -> None: +def init(db_engine: Engine) -> None: try: # Try to create session to check if DB is awake - with Session(engine) as session: + with Session(db_engine) as session: session.exec(select(1)) except Exception as e: logger.error(e) @@ -30,7 +31,7 @@ def init() -> None: def main() -> None: logger.info("Initializing service") - init() + init(engine) logger.info("Service finished initializing") diff --git a/backend/app/crud.py b/backend/app/crud.py index ab665cb090..7488700a91 100644 --- a/backend/app/crud.py +++ b/backend/app/crud.py @@ -47,6 +47,7 @@ def authenticate(*, session: Session, email: str, password: str) -> User | None: return None return db_user + def create_item(*, session: Session, item_in: ItemCreate, owner_id: int) -> Item: db_item = Item.model_validate(item_in, update={"owner_id": owner_id}) session.add(db_item) diff --git a/backend/app/tests/api/api_v1/test_items.py b/backend/app/tests/api/api_v1/test_items.py index 0c92dd9c01..20eca1e575 100644 --- a/backend/app/tests/api/api_v1/test_items.py +++ b/backend/app/tests/api/api_v1/test_items.py @@ -36,3 +36,127 @@ def test_read_item( assert content["description"] == item.description assert content["id"] == item.id assert content["owner_id"] == item.owner_id + + +def test_read_item_not_found( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + response = client.get( + f"{settings.API_V1_STR}/items/999", + headers=superuser_token_headers, + ) + assert response.status_code == 404 + content = response.json() + assert content["detail"] == "Item not found" + + +def test_read_item_not_enough_permissions( + client: TestClient, normal_user_token_headers: dict, db: Session +) -> None: + item = create_random_item(db) + response = client.get( + f"{settings.API_V1_STR}/items/{item.id}", + headers=normal_user_token_headers, + ) + assert response.status_code == 400 + content = response.json() + assert content["detail"] == "Not enough permissions" + + +def test_read_items( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + create_random_item(db) + create_random_item(db) + response = client.get( + f"{settings.API_V1_STR}/items/", + headers=superuser_token_headers, + ) + assert response.status_code == 200 + content = response.json() + assert len(content["data"]) >= 2 + + +def test_update_item( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + item = create_random_item(db) + data = {"title": "Updated title", "description": "Updated description"} + response = client.put( + f"{settings.API_V1_STR}/items/{item.id}", + headers=superuser_token_headers, + json=data, + ) + assert response.status_code == 200 + content = response.json() + assert content["title"] == data["title"] + assert content["description"] == data["description"] + assert content["id"] == item.id + assert content["owner_id"] == item.owner_id + + +def test_update_item_not_found( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + data = {"title": "Updated title", "description": "Updated description"} + response = client.put( + f"{settings.API_V1_STR}/items/999", + headers=superuser_token_headers, + json=data, + ) + assert response.status_code == 404 + content = response.json() + assert content["detail"] == "Item not found" + + +def test_update_item_not_enough_permissions( + client: TestClient, normal_user_token_headers: dict, db: Session +) -> None: + item = create_random_item(db) + data = {"title": "Updated title", "description": "Updated description"} + response = client.put( + f"{settings.API_V1_STR}/items/{item.id}", + headers=normal_user_token_headers, + json=data, + ) + assert response.status_code == 400 + content = response.json() + assert content["detail"] == "Not enough permissions" + + +def test_delete_item( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + item = create_random_item(db) + response = client.delete( + f"{settings.API_V1_STR}/items/{item.id}", + headers=superuser_token_headers, + ) + assert response.status_code == 200 + content = response.json() + assert content["message"] == "Item deleted successfully" + + +def test_delete_item_not_found( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + response = client.delete( + f"{settings.API_V1_STR}/items/999", + headers=superuser_token_headers, + ) + assert response.status_code == 404 + content = response.json() + assert content["detail"] == "Item not found" + + +def test_delete_item_not_enough_permissions( + client: TestClient, normal_user_token_headers: dict, db: Session +) -> None: + item = create_random_item(db) + response = client.delete( + f"{settings.API_V1_STR}/items/{item.id}", + headers=normal_user_token_headers, + ) + assert response.status_code == 400 + content = response.json() + assert content["detail"] == "Not enough permissions" diff --git a/backend/app/tests/api/api_v1/test_login.py b/backend/app/tests/api/api_v1/test_login.py index a8b33223b6..5415fd83df 100644 --- a/backend/app/tests/api/api_v1/test_login.py +++ b/backend/app/tests/api/api_v1/test_login.py @@ -15,6 +15,15 @@ def test_get_access_token(client: TestClient) -> None: assert tokens["access_token"] +def test_get_access_token_incorrect_password(client: TestClient) -> None: + login_data = { + "username": settings.FIRST_SUPERUSER, + "password": "incorrect", + } + r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data) + assert r.status_code == 400 + + def test_use_access_token( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: @@ -25,3 +34,64 @@ def test_use_access_token( result = r.json() assert r.status_code == 200 assert "email" in result + + +def test_recovery_password( + client: TestClient, normal_user_token_headers: dict[str, str], mocker +) -> None: + mocker.patch("app.utils.send_reset_password_email", return_value=None) + mocker.patch("app.utils.send_email", return_value=None) + email = "test@example.com" + r = client.post( + f"{settings.API_V1_STR}/password-recovery/{email}", + headers=normal_user_token_headers, + ) + assert r.status_code == 200 + assert r.json() == {"message": "Password recovery email sent"} + + +def test_recovery_password_user_not_exits( + client: TestClient, normal_user_token_headers: dict[str, str] +) -> None: + email = "jVgQr@example.com" + r = client.post( + f"{settings.API_V1_STR}/password-recovery/{email}", + headers=normal_user_token_headers, + ) + assert r.status_code == 404 + + +def test_reset_password( + client: TestClient, superuser_token_headers: dict[str, str] +) -> None: + login_data = { + "username": settings.FIRST_SUPERUSER, + "password": settings.FIRST_SUPERUSER_PASSWORD, + } + r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data) + token = r.json().get("access_token") + + data = {"new_password": "changethis", "token": token} + r = client.post( + f"{settings.API_V1_STR}/reset-password/", + headers=superuser_token_headers, + json=data + ) + assert r.status_code == 200 + assert r.json() == {"message": "Password updated successfully"} + + +def test_reset_password_invalid_token( + client: TestClient, superuser_token_headers: dict[str, str] +) -> None: + data = {"new_password": "changethis", "token": "invalid"} + r = client.post( + f"{settings.API_V1_STR}/reset-password/", + headers=superuser_token_headers, + json=data + ) + response = r.json() + + assert "detail" in response + assert r.status_code == 400 + assert response["detail"] == "Invalid token" diff --git a/backend/app/tests/api/api_v1/test_users.py b/backend/app/tests/api/api_v1/test_users.py index 17d5465798..69b66579a0 100644 --- a/backend/app/tests/api/api_v1/test_users.py +++ b/backend/app/tests/api/api_v1/test_users.py @@ -30,8 +30,10 @@ def test_get_users_normal_user_me( def test_create_user_new_email( - client: TestClient, superuser_token_headers: dict, db: Session + client: TestClient, superuser_token_headers: dict, db: Session, mocker ) -> None: + mocker.patch("app.utils.send_new_account_email") + mocker.patch("app.core.config.settings.EMAILS_ENABLED", True) username = random_email() password = random_lower_string() data = {"email": username, "password": password} @@ -66,6 +68,46 @@ def test_get_existing_user( assert existing_user.email == api_user["email"] +def test_get_existing_user_current_user( + client: TestClient, db: Session +) -> None: + username = random_email() + password = random_lower_string() + user_in = UserCreate(email=username, password=password) + user = crud.create_user(session=db, user_create=user_in) + user_id = user.id + + login_data = { + "username": username, + "password": password, + } + r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data) + tokens = r.json() + a_token = tokens["access_token"] + headers = {"Authorization": f"Bearer {a_token}"} + + r = client.get( + f"{settings.API_V1_STR}/users/{user_id}", + headers=headers, + ) + assert 200 <= r.status_code < 300 + api_user = r.json() + existing_user = crud.get_user_by_email(session=db, email=username) + assert existing_user + assert existing_user.email == api_user["email"] + + +def test_get_existing_user_permissions_error( + client: TestClient, normal_user_token_headers: dict[str, str], db: Session +) -> None: + r = client.get( + f"{settings.API_V1_STR}/users/999999", + headers=normal_user_token_headers, + ) + assert r.status_code == 403 + assert r.json() == {"detail": "The user doesn't have enough privileges"} + + def test_create_user_existing_username( client: TestClient, superuser_token_headers: dict, db: Session ) -> None: @@ -119,3 +161,223 @@ def test_retrieve_users( assert "count" in all_users for item in all_users["data"]: assert "email" in item + + +def test_update_user_me( + client: TestClient, normal_user_token_headers: dict[str, str], db: Session +) -> None: + full_name = "Updated Name" + email = "updated email" + data = {"full_name": full_name, "email": email} + r = client.patch( + f"{settings.API_V1_STR}/users/me", + headers=normal_user_token_headers, + json=data, + ) + assert r.status_code == 200 + updated_user = r.json() + assert updated_user["email"] == email + assert updated_user["full_name"] == full_name + + +def test_update_password_me( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + new_password = random_lower_string() + data = {"current_password": settings.FIRST_SUPERUSER_PASSWORD, "new_password": new_password} + r = client.patch( + f"{settings.API_V1_STR}/users/me/password", + headers=superuser_token_headers, + json=data, + ) + assert r.status_code == 200 + updated_user = r.json() + assert updated_user["message"] == "Password updated successfully" + + # Revert to the old password to keep consistency in test + old_data = {"current_password": new_password, "new_password": settings.FIRST_SUPERUSER_PASSWORD} + r = client.patch( + f"{settings.API_V1_STR}/users/me/password", + headers=superuser_token_headers, + json=old_data, + ) + assert r.status_code == 200 + + +def test_update_password_me_incorrect_password( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + new_password = random_lower_string() + data = {"current_password": new_password, "new_password": new_password} + r = client.patch( + f"{settings.API_V1_STR}/users/me/password", + headers=superuser_token_headers, + json=data, + ) + assert r.status_code == 400 + updated_user = r.json() + assert updated_user["detail"] == "Incorrect password" + + +def test_update_password_me_same_password_error( + client: TestClient, superuser_token_headers: dict, db: Session +) -> None: + data = {"current_password": settings.FIRST_SUPERUSER_PASSWORD, "new_password": settings.FIRST_SUPERUSER_PASSWORD} + r = client.patch( + f"{settings.API_V1_STR}/users/me/password", + headers=superuser_token_headers, + json=data, + ) + assert r.status_code == 400 + updated_user = r.json() + assert updated_user["detail"] == "New password cannot be the same as the current one" + + +def test_create_user_open( + client: TestClient, mocker +) -> None: + mocker.patch("app.core.config.settings.USERS_OPEN_REGISTRATION", True) + username = random_email() + password = random_lower_string() + full_name = random_lower_string() + data = {"email": username, "password": password, "full_name": full_name} + r = client.post( + f"{settings.API_V1_STR}/users/open", + json=data, + ) + assert r.status_code == 200 + created_user = r.json() + assert created_user["email"] == username + assert created_user["full_name"] == full_name + + +def test_create_user_open_forbidden_error( + client: TestClient, mocker +) -> None: + mocker.patch("app.core.config.settings.USERS_OPEN_REGISTRATION", False) + username = random_email() + password = random_lower_string() + full_name = random_lower_string() + data = {"email": username, "password": password, "full_name": full_name} + r = client.post( + f"{settings.API_V1_STR}/users/open", + json=data, + ) + assert r.status_code == 403 + assert r.json()["detail"] == "Open user registration is forbidden on this server" + + +def test_create_user_open_already_exists_error( + client: TestClient, mocker +) -> None: + mocker.patch("app.core.config.settings.USERS_OPEN_REGISTRATION", True) + password = random_lower_string() + full_name = random_lower_string() + data = {"email": settings.FIRST_SUPERUSER, "password": password, "full_name": full_name} + r = client.post( + f"{settings.API_V1_STR}/users/open", + json=data, + ) + assert r.status_code == 400 + assert r.json()["detail"] == "The user with this username already exists in the system" + + +def test_update_user( + client: TestClient, superuser_token_headers: dict[str, str], db: Session +) -> None: + username = random_email() + password = random_lower_string() + user_in = UserCreate(email=username, password=password) + user = crud.create_user(session=db, user_create=user_in) + + data = {"full_name": "Updated_full_name"} + r = client.patch( + f"{settings.API_V1_STR}/users/{user.id}", + headers=superuser_token_headers, + json=data, + ) + assert r.status_code == 200 + updated_user = r.json() + assert updated_user["full_name"] == "Updated_full_name" + + +def test_update_user_not_exists( + client: TestClient, superuser_token_headers: dict[str, str], db: Session +) -> None: + data = {"full_name": "Updated_full_name"} + r = client.patch( + f"{settings.API_V1_STR}/users/99999999", + headers=superuser_token_headers, + json=data, + ) + assert r.status_code == 404 + assert r.json()["detail"] == "The user with this username does not exist in the system" + + +def test_delete_user_super_user( + client: TestClient, superuser_token_headers: dict[str, str], db: Session +) -> None: + username = random_email() + password = random_lower_string() + user_in = UserCreate(email=username, password=password) + user = crud.create_user(session=db, user_create=user_in) + user_id = user.id + r = client.delete( + f"{settings.API_V1_STR}/users/{user_id}", + headers=superuser_token_headers, + ) + assert r.status_code == 200 + deleted_user = r.json() + assert deleted_user["message"] == "User deleted successfully" + + +def test_delete_user_current_user( + client: TestClient, db: Session +) -> None: + username = random_email() + password = random_lower_string() + user_in = UserCreate(email=username, password=password) + user = crud.create_user(session=db, user_create=user_in) + user_id = user.id + + login_data = { + "username": username, + "password": password, + } + r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data) + tokens = r.json() + a_token = tokens["access_token"] + headers = {"Authorization": f"Bearer {a_token}"} + + r = client.delete( + f"{settings.API_V1_STR}/users/{user_id}", + headers=headers, + ) + assert r.status_code == 200 + deleted_user = r.json() + assert deleted_user["message"] == "User deleted successfully" + + +def test_delete_user_not_found( + client: TestClient, superuser_token_headers: dict[str, str], db: Session +) -> None: + r = client.delete( + f"{settings.API_V1_STR}/users/99999999", + headers=superuser_token_headers, + ) + assert r.status_code == 404 + assert r.json()["detail"] == "User not found" + + +def test_delete_user_current_super_user_error( + client: TestClient, superuser_token_headers: dict[str, str], db: Session +) -> None: + super_user = crud.get_user_by_email(session=db, email=settings.FIRST_SUPERUSER) + user_id = super_user.id + + r = client.delete( + f"{settings.API_V1_STR}/users/{user_id}", + headers=superuser_token_headers, + ) + assert r.status_code == 403 + assert r.json()["detail"] == "Super users are not allowed to delete themselves" diff --git a/backend/app/tests/scripts/__init__.py b/backend/app/tests/scripts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/app/tests/scripts/test_backend_pre_start.py b/backend/app/tests/scripts/test_backend_pre_start.py new file mode 100644 index 0000000000..29173f996e --- /dev/null +++ b/backend/app/tests/scripts/test_backend_pre_start.py @@ -0,0 +1,28 @@ +from unittest.mock import MagicMock + +from sqlmodel import select + +from app.backend_pre_start import init, logger + + +def test_init_successful_connection(mocker): + engine_mock = MagicMock() + + session_mock = MagicMock() + exec_mock = MagicMock(return_value=True) + session_mock.configure_mock(**{'exec.return_value': exec_mock}) + mocker.patch('sqlmodel.Session', return_value=session_mock) + + mocker.patch.object(logger, 'info') + mocker.patch.object(logger, 'error') + mocker.patch.object(logger, 'warn') + + try: + init(engine_mock) + connection_successful = True + except Exception: + connection_successful = False + + assert connection_successful, "The database connection should be successful and not raise an exception." + + assert session_mock.exec.called_once_with(select(1)), "The session should execute a select statement once." diff --git a/backend/app/tests/scripts/test_celery_pre_start.py b/backend/app/tests/scripts/test_celery_pre_start.py new file mode 100644 index 0000000000..67c3f5cbe0 --- /dev/null +++ b/backend/app/tests/scripts/test_celery_pre_start.py @@ -0,0 +1,28 @@ +from unittest.mock import MagicMock + +from sqlmodel import select + +from app.celeryworker_pre_start import init, logger + + +def test_init_successful_connection(mocker): + engine_mock = MagicMock() + + session_mock = MagicMock() + exec_mock = MagicMock(return_value=True) + session_mock.configure_mock(**{'exec.return_value': exec_mock}) + mocker.patch('sqlmodel.Session', return_value=session_mock) + + mocker.patch.object(logger, 'info') + mocker.patch.object(logger, 'error') + mocker.patch.object(logger, 'warn') + + try: + init(engine_mock) + connection_successful = True + except Exception: + connection_successful = False + + assert connection_successful, "The database connection should be successful and not raise an exception." + + assert session_mock.exec.called_once_with(select(1)), "The session should execute a select statement once." diff --git a/backend/app/tests/scripts/test_test_pre_start.py b/backend/app/tests/scripts/test_test_pre_start.py new file mode 100644 index 0000000000..485338d4e2 --- /dev/null +++ b/backend/app/tests/scripts/test_test_pre_start.py @@ -0,0 +1,28 @@ +from unittest.mock import MagicMock + +from sqlmodel import select + +from app.tests_pre_start import init, logger + + +def test_init_successful_connection(mocker): + engine_mock = MagicMock() + + session_mock = MagicMock() + exec_mock = MagicMock(return_value=True) + session_mock.configure_mock(**{'exec.return_value': exec_mock}) + mocker.patch('sqlmodel.Session', return_value=session_mock) + + mocker.patch.object(logger, 'info') + mocker.patch.object(logger, 'error') + mocker.patch.object(logger, 'warn') + + try: + init(engine_mock) + connection_successful = True + except Exception: + connection_successful = False + + assert connection_successful, "The database connection should be successful and not raise an exception." + + assert session_mock.exec.called_once_with(select(1)), "The session should execute a select statement once." diff --git a/backend/app/tests_pre_start.py b/backend/app/tests_pre_start.py index a9336023b3..0ce6045635 100644 --- a/backend/app/tests_pre_start.py +++ b/backend/app/tests_pre_start.py @@ -1,5 +1,6 @@ import logging +from sqlalchemy import Engine from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed @@ -18,10 +19,10 @@ before=before_log(logger, logging.INFO), after=after_log(logger, logging.WARN), ) -def init() -> None: +def init(db_engine: Engine) -> None: try: # Try to create session to check if DB is awake - with Session(engine) as session: + with Session(db_engine) as session: session.exec(select(1)) except Exception as e: logger.error(e) @@ -30,7 +31,7 @@ def init() -> None: def main() -> None: logger.info("Initializing service") - init() + init(engine) logger.info("Service finished initializing") diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 1f5bc80eb3..54415685b3 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -34,6 +34,7 @@ pytest-cov = "^4.1.0" mypy = "^1.8.0" ruff = "^0.2.2" pre-commit = "^3.6.2" +pytest-mock = "^3.12.0" [tool.isort] multi_line_output = 3 From 0eb134bb081e81ad5a09e7e91f3de1db9143c0a2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Mar 2024 23:22:02 +0000 Subject: [PATCH 241/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index e530a3293f..65e0ac763b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -11,6 +11,7 @@ ### Features +* ✅ Add tests to raise coverage to at least 90% and fix recover password logic. PR [#632](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/632) by [@estebanx64](https://github.com/estebanx64). * ⚙️ Add Prettier and ESLint config with pre-commit. PR [#640](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/640) by [@alejsdev](https://github.com/alejsdev). * 👷 Add coverage with Smokeshow to CI and badge. PR [#638](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/638) by [@estebanx64](https://github.com/estebanx64). * ✨ Migrate to TanStack Query (React Query) and TanStack Router. PR [#637](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/637) by [@alejsdev](https://github.com/alejsdev). From 47fb928681c538cabfc995bb3cf2ea8c29e6f4be Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:10:09 +0100 Subject: [PATCH 242/771] =?UTF-8?q?=E2=8F=AA=20Revert=20"=E2=9A=99?= =?UTF-8?q?=EF=B8=8F=20Add=20Prettier=20and=20ESLint=20config=20with=20pre?= =?UTF-8?q?-commit"=20(#644)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1b8b42cee5b0a8253cbab5e538bf6c3bbb5ec85e. --- .pre-commit-config.yaml | 51 +- new-frontend/.eslintignore | 1 - new-frontend/.eslintrc.cjs | 18 + new-frontend/.eslintrc.json | 13 - new-frontend/.prettierignore | 1 - new-frontend/.prettierrc | 10 - new-frontend/package-lock.json | 3967 +++----------------------------- new-frontend/package.json | 12 +- 8 files changed, 361 insertions(+), 3712 deletions(-) delete mode 100644 new-frontend/.eslintignore create mode 100644 new-frontend/.eslintrc.cjs delete mode 100644 new-frontend/.eslintrc.json delete mode 100644 new-frontend/.prettierignore delete mode 100644 new-frontend/.prettierrc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d5a12745a..984c6caa0a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,46 +1,25 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks +default_language_version: + python: python3.10 repos: - - repo: https://github.com/pre-commit/pre-commit-hooks +- repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: check-added-large-files - - id: check-toml - - id: check-yaml + - id: check-added-large-files + - id: check-toml + - id: check-yaml args: - - --unsafe - - id: end-of-file-fixer - - id: trailing-whitespace - - repo: https://github.com/charliermarsh/ruff-pre-commit + - --unsafe + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/charliermarsh/ruff-pre-commit rev: v0.2.2 hooks: - - id: ruff + - id: ruff args: - - --fix - - id: ruff-format - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.1.0 - hooks: - - id: prettier - additional_dependencies: - - prettier@3.2.5 - - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.56.0 - hooks: - - id: eslint - files: \.(js|jsx|ts|tsx)$ - additional_dependencies: - - eslint@8.57.0 - - eslint-config-standard-with-typescript@43.0.1 - - eslint-plugin-import@2.29.1 - - eslint-plugin-n@16.6.2 - - eslint-plugin-promise@6.1.1 - - eslint-plugin-react@7.34.0 - - eslint-plugin-react-hooks@4.6.0 - - eslint-plugin-react-refresh@0.4.4 - - "@typescript-eslint/eslint-plugin@6.21.0" - - "@typescript-eslint/parser@6.10.0" - + - --fix + - id: ruff-format ci: - autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks - autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate + autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks + autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate diff --git a/new-frontend/.eslintignore b/new-frontend/.eslintignore deleted file mode 100644 index c950e9ce1c..0000000000 --- a/new-frontend/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -new-frontend/src/client/** diff --git a/new-frontend/.eslintrc.cjs b/new-frontend/.eslintrc.cjs new file mode 100644 index 0000000000..d6c9537953 --- /dev/null +++ b/new-frontend/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/new-frontend/.eslintrc.json b/new-frontend/.eslintrc.json deleted file mode 100644 index cfd443bb31..0000000000 --- a/new-frontend/.eslintrc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": ["standard-with-typescript", "plugin:react/recommended"], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": ["react"], - "rules": {} -} diff --git a/new-frontend/.prettierignore b/new-frontend/.prettierignore deleted file mode 100644 index c950e9ce1c..0000000000 --- a/new-frontend/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -new-frontend/src/client/** diff --git a/new-frontend/.prettierrc b/new-frontend/.prettierrc deleted file mode 100644 index c4635f89c8..0000000000 --- a/new-frontend/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "semi": true, - "trailingComma": "all", - "singleQuote": true, - "printWidth": 80, - "tabWidth": 2, - "importOrder": ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"], - "importOrderSeparation": true, - "importOrderSortSpecifiers": true -} diff --git a/new-frontend/package-lock.json b/new-frontend/package-lock.json index 05a30618de..13f87612bf 100644 --- a/new-frontend/package-lock.json +++ b/new-frontend/package-lock.json @@ -29,20 +29,14 @@ "@types/node": "20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", - "@typescript-eslint/eslint-plugin": "6.21.0", + "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "@vitejs/plugin-react-swc": "^3.5.0", - "eslint": "8.57.0", - "eslint-config-standard-with-typescript": "43.0.1", - "eslint-plugin-import": "2.29.1", - "eslint-plugin-n": "16.6.2", - "eslint-plugin-promise": "6.1.1", - "eslint-plugin-react": "7.34.0", + "eslint": "^8.53.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.4", "openapi-typescript-codegen": "0.25.0", - "prettier": "3.2.5", - "typescript": "5.4.2", + "typescript": "^5.2.2", "vite": "^5.0.0" } }, @@ -259,9 +253,9 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -1961,9 +1955,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -1983,23 +1977,45 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/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/@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==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "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==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", "minimatch": "^3.0.5" }, "engines": { @@ -2020,9 +2036,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@jsdevtools/ono": { @@ -2546,12 +2562,6 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, "node_modules/@types/lodash": { "version": "4.14.202", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", @@ -2612,22 +2622,22 @@ "devOptional": true }, "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", + "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/type-utils": "6.13.1", + "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2652,53 +2662,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/parser": { "version": "6.13.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", @@ -2745,13 +2708,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", + "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/utils": "6.13.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2771,88 +2734,6 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/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/@typescript-eslint/type-utils/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/types": { "version": "6.13.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", @@ -2894,17 +2775,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", + "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", "semver": "^7.5.4" }, "engines": { @@ -2918,105 +2799,6 @@ "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/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/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "6.13.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", @@ -3071,9 +2853,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3091,22 +2873,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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", @@ -3148,41 +2914,6 @@ "node": ">=10" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -3192,193 +2923,29 @@ "node": ">=8" } }, - "node_modules/array.prototype.filter": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", - "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", - "dev": true, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz", - "integrity": "sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==", - "dev": true, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", - "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.toreversed": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", - "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", - "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.1.0", - "es-shim-unscopables": "^1.0.2" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" }, "engines": { "node": ">=10", @@ -3434,46 +3001,6 @@ "unload": "2.2.0" } }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -3672,40 +3199,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3756,154 +3249,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.22.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", - "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.1", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.0", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-iterator-helpers": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", - "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", - "dev": true, - "dependencies": { - "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.4", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.2", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/esbuild": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", @@ -3953,16 +3298,16 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "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", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -4007,350 +3352,76 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-compat-utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", - "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", "dev": true, "engines": { - "node": ">=12" + "node": ">=10" }, "peerDependencies": { - "eslint": ">=6.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, - "node_modules/eslint-config-standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.4.tgz", + "integrity": "sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==", "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" - } - ], - "engines": { - "node": ">=12.0.0" - }, "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", - "eslint-plugin-promise": "^6.0.0" + "eslint": ">=7" } }, - "node_modules/eslint-config-standard-with-typescript": { - "version": "43.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-43.0.1.tgz", - "integrity": "sha512-WfZ986+qzIzX6dcr4yGUyVb/l9N3Z8wPXCc5z/70fljs3UbWhhV+WxrfgsqMToRzuuyX9MqZ974pq2UPhDTOcA==", + "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": { - "@typescript-eslint/parser": "^6.4.0", - "eslint-config-standard": "17.1.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6.4.0", - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", - "eslint-plugin-promise": "^6.0.0", - "typescript": "*" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "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, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es-x": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", - "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0", - "eslint-compat-utils": "^0.1.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": ">=8" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-n": { - "version": "16.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", - "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.5.0", - "get-tsconfig": "^4.7.0", - "globals": "^13.24.0", - "ignore": "^5.2.4", - "is-builtin-module": "^3.2.1", - "is-core-module": "^2.12.1", - "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.3" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.34.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz", - "integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlast": "^1.2.4", - "array.prototype.flatmap": "^1.3.2", - "array.prototype.toreversed": "^1.1.2", - "array.prototype.tosorted": "^1.1.3", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.17", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.7", - "object.fromentries": "^2.0.7", - "object.hasown": "^1.1.3", - "object.values": "^1.1.7", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.10" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.4.tgz", - "integrity": "sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==", - "dev": true, - "peerDependencies": { - "eslint": ">=7" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "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==", + "node_modules/eslint/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": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "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": { - "url": "https://opencollective.com/eslint" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "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/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/espree": { "version": "9.6.1", @@ -4561,15 +3632,6 @@ } } }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -4675,52 +3737,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -4729,35 +3745,6 @@ "node": ">=6" } }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", - "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", - "dev": true, - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4790,9 +3777,9 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -4804,21 +3791,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -4848,18 +3820,6 @@ "csstype": "^3.0.10" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4891,508 +3851,145 @@ }, "optionalDependencies": { "uglify-js": "^3.1.4" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "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==", - "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==", - "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==" - }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", + } + }, + "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/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 4" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, + "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==", "dependencies": { - "has-tostringtag": "^1.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.8.19" } }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, + "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==", "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" + "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==" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "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, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "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": { - "call-bind": "^1.0.2" + "is-extglob": "^2.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.12.0" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "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/isexe": { "version": "2.0.0", @@ -5400,19 +3997,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/iterator.prototype": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", - "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" - } - }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -5459,30 +4043,12 @@ "node": ">=10" } }, - "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/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -5495,21 +4061,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5713,116 +4264,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", - "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", - "dev": true, - "dependencies": { - "array.prototype.filter": "^1.0.3", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0" - } - }, - "node_modules/object.hasown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", - "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/oblivious-set": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", @@ -5984,15 +4425,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -6271,50 +4703,11 @@ } } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", - "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0", - "get-intrinsic": "^1.2.3", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/remove-accents": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", @@ -6341,16 +4734,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "node": ">=4" } }, "node_modules/reusify": { @@ -6428,41 +4812,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-array-concat": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", - "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -6486,38 +4835,6 @@ "node": ">=10" } }, - "node_modules/set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.2", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6539,24 +4856,6 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6584,71 +4883,6 @@ "node": ">=0.10.0" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", - "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "regexp.prototype.flags": "^1.5.0", - "set-function-name": "^2.0.0", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6661,15 +4895,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6763,18 +4988,6 @@ "typescript": ">=4.2.0" } }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -6804,83 +5017,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6903,21 +5043,6 @@ "node": ">=0.8.0" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -7070,82 +5195,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", - "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", - "dev": true, - "dependencies": { - "function.prototype.name": "^1.1.5", - "has-tostringtag": "^1.0.0", - "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", - "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", - "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.6", - "call-bind": "^1.0.5", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -7387,9 +5436,9 @@ } }, "@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "requires": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -8581,9 +6630,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -8595,22 +6644,42 @@ "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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 + } } }, "@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==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", "minimatch": "^3.0.5" } }, @@ -8621,9 +6690,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@jsdevtools/ono": { @@ -8908,12 +6977,6 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, "@types/lodash": { "version": "4.14.202", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", @@ -8974,56 +7037,28 @@ "devOptional": true }, "@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", + "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/type-utils": "6.13.1", + "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", "natural-compare": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" - }, - "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - } - }, - "@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true - }, - "@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - } - } } }, "@typescript-eslint/parser": { @@ -9050,67 +7085,15 @@ } }, "@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", + "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/utils": "6.13.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" - }, - "dependencies": { - "@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - } - }, - "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, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } } }, "@typescript-eslint/types": { @@ -9135,80 +7118,18 @@ } }, "@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", + "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - }, - "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - } - }, - "@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - } - }, - "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, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", + "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { @@ -9255,9 +7176,9 @@ } }, "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true }, "acorn-jsx": { @@ -9267,18 +7188,6 @@ "dev": true, "requires": {} }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -9308,162 +7217,17 @@ "tslib": "^2.0.0" } }, - "array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - } - }, - "array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-string": "^1.0.7" - } - }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "array.prototype.filter": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", - "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, - "array.prototype.findlast": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz", - "integrity": "sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - } - }, - "array.prototype.findlastindex": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", - "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - } - }, - "array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.toreversed": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", - "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.tosorted": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", - "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.1.0", - "es-shim-unscopables": "^1.0.2" - } - }, - "arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - } - }, - "asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.3" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "requires": { - "possible-typed-array-names": "^1.0.0" - } - }, "axios": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", @@ -9527,34 +7291,6 @@ "unload": "2.2.0" } }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "requires": { - "semver": "^7.0.0" - } - }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - } - }, "call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -9705,28 +7441,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "requires": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9768,130 +7482,6 @@ "is-arrayish": "^0.2.1" } }, - "es-abstract": { - "version": "1.22.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", - "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.1", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.0", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.14" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4" - } - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true - }, - "es-iterator-helpers": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", - "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", - "dev": true, - "requires": { - "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.4", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.2", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.1.0" - } - }, - "es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - } - }, - "es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "requires": { - "hasown": "^2.0.0" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, "esbuild": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", @@ -9928,16 +7518,16 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "requires": { "@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", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -9971,212 +7561,24 @@ "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" - } - }, - "eslint-compat-utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", - "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", - "dev": true, - "requires": {} - }, - "eslint-config-standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", - "dev": true, - "requires": {} - }, - "eslint-config-standard-with-typescript": { - "version": "43.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-43.0.1.tgz", - "integrity": "sha512-WfZ986+qzIzX6dcr4yGUyVb/l9N3Z8wPXCc5z/70fljs3UbWhhV+WxrfgsqMToRzuuyX9MqZ974pq2UPhDTOcA==", - "dev": true, - "requires": { - "@typescript-eslint/parser": "^6.4.0", - "eslint-config-standard": "17.1.0" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-es-x": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", - "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0", - "eslint-compat-utils": "^0.1.2" - } - }, - "eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "dev": true, - "requires": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" }, "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "eslint-plugin-n": { - "version": "16.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", - "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.4.0", - "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.5.0", - "get-tsconfig": "^4.7.0", - "globals": "^13.24.0", - "ignore": "^5.2.4", - "is-builtin-module": "^3.2.1", - "is-core-module": "^2.12.1", - "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.3" - } - }, - "eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "requires": {} - }, - "eslint-plugin-react": { - "version": "7.34.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz", - "integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==", - "dev": true, - "requires": { - "array-includes": "^3.1.7", - "array.prototype.findlast": "^1.2.4", - "array.prototype.flatmap": "^1.3.2", - "array.prototype.toreversed": "^1.1.2", - "array.prototype.tosorted": "^1.1.3", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.17", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.7", - "object.fromentries": "^2.0.7", - "object.hasown": "^1.1.3", - "object.values": "^1.1.7", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.10" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "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 } } @@ -10366,15 +7768,6 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -10454,62 +7847,11 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, - "function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, "get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" }, - "get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - } - }, - "get-tsconfig": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", - "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", - "dev": true, - "requires": { - "resolve-pkg-maps": "^1.0.0" - } - }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -10533,23 +7875,14 @@ } }, "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, - "globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3" - } - }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -10571,15 +7904,6 @@ "dev": true, "requires": {} }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -10605,48 +7929,12 @@ "wordwrap": "^1.0.0" } }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.3" - } - }, "hasown": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", @@ -10696,18 +7984,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - } + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "invariant": { "version": "2.2.4", @@ -10717,64 +7994,11 @@ "loose-envify": "^1.0.0" } }, - "is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, - "is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "requires": { - "builtin-modules": "^3.3.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, "is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -10783,39 +8007,12 @@ "hasown": "^2.0.0" } }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, - "is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -10825,141 +8022,24 @@ "is-extglob": "^2.1.1" } }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, "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 }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "requires": { - "call-bind": "^1.0.7" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "requires": { - "which-typed-array": "^1.1.14" - } - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "iterator.prototype": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", - "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", - "dev": true, - "requires": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" - } - }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -10999,27 +8079,12 @@ "@apidevtools/json-schema-ref-parser": "9.0.9" } }, - "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 - }, "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 }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -11030,18 +8095,6 @@ "universalify": "^2.0.0" } }, - "jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - } - }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -11197,86 +8250,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, - "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - } - }, - "object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - } - }, - "object.groupby": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", - "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", - "dev": true, - "requires": { - "array.prototype.filter": "^1.0.3", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0" - } - }, - "object.hasown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", - "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", - "dev": true, - "requires": { - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - } - }, - "object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - } - }, "oblivious-set": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", @@ -11393,12 +8366,6 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, - "possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true - }, "postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -11550,38 +8517,11 @@ "tslib": "^2.0.0" } }, - "reflect.getprototypeof": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", - "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0", - "get-intrinsic": "^1.2.3", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" - } - }, "regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, - "regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, - "requires": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - } - }, "remove-accents": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", @@ -11602,12 +8542,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, - "resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -11652,29 +8586,6 @@ "queue-microtask": "^1.2.2" } }, - "safe-array-concat": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", - "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - } - }, - "safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, - "requires": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - } - }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -11692,32 +8603,6 @@ "lru-cache": "^6.0.0" } }, - "set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", - "dev": true, - "requires": { - "define-data-property": "^1.1.2", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" - } - }, - "set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11733,18 +8618,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - } - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -11763,56 +8636,6 @@ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, - "string.prototype.matchall": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", - "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "regexp.prototype.flags": "^1.5.0", - "set-function-name": "^2.0.0", - "side-channel": "^1.0.4" - } - }, - "string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - } - }, - "string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11822,12 +8645,6 @@ "ansi-regex": "^5.0.1" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -11895,18 +8712,6 @@ "dev": true, "requires": {} }, - "tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -11927,62 +8732,10 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, - "typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - } - }, - "typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - } - }, - "typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - } - }, - "typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - } - }, "typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true }, "uglify-js": { @@ -11992,18 +8745,6 @@ "dev": true, "optional": true }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -12078,64 +8819,6 @@ "isexe": "^2.0.0" } }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-builtin-type": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", - "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", - "dev": true, - "requires": { - "function.prototype.name": "^1.1.5", - "has-tostringtag": "^1.0.0", - "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", - "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", - "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.6", - "call-bind": "^1.0.5", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.1" - } - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/new-frontend/package.json b/new-frontend/package.json index 94777e2a05..11b2bd72d3 100644 --- a/new-frontend/package.json +++ b/new-frontend/package.json @@ -32,20 +32,14 @@ "@types/node": "20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", - "@typescript-eslint/eslint-plugin": "6.21.0", + "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "@vitejs/plugin-react-swc": "^3.5.0", - "eslint": "8.57.0", - "eslint-config-standard-with-typescript": "43.0.1", - "eslint-plugin-import": "2.29.1", - "eslint-plugin-n": "16.6.2", - "eslint-plugin-promise": "6.1.1", - "eslint-plugin-react": "7.34.0", + "eslint": "^8.53.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.4", "openapi-typescript-codegen": "0.25.0", - "prettier": "3.2.5", - "typescript": "5.4.2", + "typescript": "^5.2.2", "vite": "^5.0.0" } } From 97db2e6fe4687de738199fa2d81c044e5e06c321 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 13:10:26 +0000 Subject: [PATCH 243/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 65e0ac763b..acdf7985d9 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⏪ Revert "⚙️ Add Prettier and ESLint config with pre-commit". PR [#644](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/644) by [@alejsdev](https://github.com/alejsdev). * 🐛 Fix copier to handle string vars with spaces in quotes. PR [#631](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/631) by [@estebanx64](https://github.com/estebanx64). * ⬆ Bump tiangolo/issue-manager from 0.2.0 to 0.5.0. PR [#591](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/591) by [@dependabot[bot]](https://github.com/apps/dependabot). * ✨ Upgrade items router with new SQLModel models, simplified logic, and new FastAPI Annotated dependencies. PR [#560](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/560) by [@tiangolo](https://github.com/tiangolo). From c5c78172ea6521d9d79c6a119305591081540b4e Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:43:02 +0100 Subject: [PATCH 244/771] =?UTF-8?q?=F0=9F=94=A7=20Update=20pre-commit=20co?= =?UTF-8?q?nfig=20(#645)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 984c6caa0a..ecd010386b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,5 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -default_language_version: - python: python3.10 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 From 7d9c74a4ba0bff19e7fcdd934fcc3bad35f00877 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 13:43:19 +0000 Subject: [PATCH 245/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index acdf7985d9..4cc305600c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -104,6 +104,7 @@ ### Internal +* 🔧 Update pre-commit config. PR [#645](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/645) by [@alejsdev](https://github.com/alejsdev). * 👷 Add dependabot. PR [#547](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/547) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix latest-changes GitHub Action token, strike 2. PR [#546](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/546) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix latest-changes GitHub Action token config. PR [#545](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/545) by [@tiangolo](https://github.com/tiangolo). From de850209896a3eca23ce9928d4ade78632dc6a30 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:58:36 +0100 Subject: [PATCH 246/771] =?UTF-8?q?=F0=9F=8E=A8=20Format=20with=20Prettier?= =?UTF-8?q?=20(#646)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- new-frontend/src/components/Admin/AddUser.tsx | 279 +++++++++++------- .../src/components/Admin/EditUser.tsx | 261 ++++++++++------ .../src/components/Common/ActionsMenu.tsx | 102 ++++--- .../src/components/Common/DeleteAlert.tsx | 171 ++++++----- new-frontend/src/components/Common/Navbar.tsx | 54 ++-- .../src/components/Common/NotFound.tsx | 56 ++-- .../src/components/Common/Sidebar.tsx | 171 +++++++---- .../src/components/Common/SidebarItems.tsx | 101 +++---- .../src/components/Common/UserMenu.tsx | 90 +++--- new-frontend/src/components/Items/AddItem.tsx | 197 +++++++------ .../src/components/Items/EditItem.tsx | 183 +++++++----- .../components/UserSettings/Appearance.tsx | 60 ++-- .../UserSettings/ChangePassword.tsx | 189 ++++++++---- .../components/UserSettings/DeleteAccount.tsx | 59 ++-- .../UserSettings/DeleteConfirmation.tsx | 177 ++++++----- .../UserSettings/UserInformation.tsx | 231 +++++++++------ new-frontend/src/hooks/useAuth.ts | 55 ++-- new-frontend/src/hooks/useCustomToast.ts | 34 ++- new-frontend/src/main.tsx | 22 +- new-frontend/src/routes/__root.tsx | 17 +- new-frontend/src/routes/_layout.tsx | 55 ++-- new-frontend/src/routes/_layout/admin.tsx | 179 ++++++----- new-frontend/src/routes/_layout/index.tsx | 35 +-- new-frontend/src/routes/_layout/items.tsx | 138 +++++---- new-frontend/src/routes/_layout/settings.tsx | 88 +++--- new-frontend/src/routes/login.tsx | 131 +++++--- new-frontend/src/routes/recover-password.tsx | 87 ++++-- new-frontend/src/routes/reset-password.tsx | 197 ++++++++----- new-frontend/src/theme.tsx | 38 +-- 29 files changed, 2081 insertions(+), 1376 deletions(-) diff --git a/new-frontend/src/components/Admin/AddUser.tsx b/new-frontend/src/components/Admin/AddUser.tsx index 68b550c0a3..fcd80f9663 100644 --- a/new-frontend/src/components/Admin/AddUser.tsx +++ b/new-frontend/src/components/Admin/AddUser.tsx @@ -1,117 +1,194 @@ -import React from 'react'; +import React from 'react' +import { + Button, + Checkbox, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { UserCreate, UsersService } from '../../client'; -import { ApiError } from '../../client/core/ApiError'; -import useCustomToast from '../../hooks/useCustomToast'; +import { UserCreate, UsersService } from '../../client' +import { ApiError } from '../../client/core/ApiError' +import useCustomToast from '../../hooks/useCustomToast' interface AddUserProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean + onClose: () => void } interface UserCreateForm extends UserCreate { - confirm_password: string; - + confirm_password: string } const AddUser: React.FC = ({ isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: { - email: '', - full_name: '', - password: '', - confirm_password: '', - is_superuser: false, - is_active: false - } - }); + const queryClient = useQueryClient() + const showToast = useCustomToast() + const { + register, + handleSubmit, + reset, + getValues, + formState: { errors, isSubmitting }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + email: '', + full_name: '', + password: '', + confirm_password: '', + is_superuser: false, + is_active: false, + }, + }) - const addUser = async (data: UserCreate) => { - await UsersService.createUser({ requestBody: data }) - } + const addUser = async (data: UserCreate) => { + await UsersService.createUser({ requestBody: data }) + } - const mutation = useMutation(addUser, { - onSuccess: () => { - showToast('Success!', 'User created successfully.', 'success'); - reset(); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('users'); - } - }); + const mutation = useMutation(addUser, { + onSuccess: () => { + showToast('Success!', 'User created successfully.', 'success') + reset() + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('users') + }, + }) - const onSubmit: SubmitHandler = (data) => { - mutation.mutate(data); - } + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data) + } - return ( - <> - + + + + Add User + + + + Email + + {errors.email && ( + {errors.email.message} + )} + + + Full name + + {errors.full_name && ( + {errors.full_name.message} + )} + + + Set Password + + {errors.password && ( + {errors.password.message} + )} + + + Confirm Password + + value === getValues().password || + 'The passwords do not match', + })} + placeholder="Password" + type="password" + /> + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + + Is superuser? + + + + + Is active? + + + + + + - - - - - - ) + Save + + + + + + + ) } -export default AddUser; \ No newline at end of file +export default AddUser diff --git a/new-frontend/src/components/Admin/EditUser.tsx b/new-frontend/src/components/Admin/EditUser.tsx index 3ca5ce0f5c..c4661cd0df 100644 --- a/new-frontend/src/components/Admin/EditUser.tsx +++ b/new-frontend/src/components/Admin/EditUser.tsx @@ -1,116 +1,183 @@ -import React from 'react'; +import React from 'react' +import { + Button, + Checkbox, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ApiError, UserOut, UserUpdate, UsersService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, UserOut, UserUpdate, UsersService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface EditUserProps { - user: UserOut; - isOpen: boolean; - onClose: () => void; + user: UserOut + isOpen: boolean + onClose: () => void } interface UserUpdateForm extends UserUpdate { - confirm_password: string; + confirm_password: string } const EditUser: React.FC = ({ user, isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); + const queryClient = useQueryClient() + const showToast = useCustomToast() - const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting, isDirty } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: user - }); + const { + register, + handleSubmit, + reset, + getValues, + formState: { errors, isSubmitting, isDirty }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: user, + }) - const updateUser = async (data: UserUpdateForm) => { - await UsersService.updateUser({ userId: user.id, requestBody: data }); - } + const updateUser = async (data: UserUpdateForm) => { + await UsersService.updateUser({ userId: user.id, requestBody: data }) + } - const mutation = useMutation(updateUser, { - onSuccess: () => { - showToast('Success!', 'User updated successfully.', 'success'); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('users'); - } - }); + const mutation = useMutation(updateUser, { + onSuccess: () => { + showToast('Success!', 'User updated successfully.', 'success') + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('users') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - if (data.password === '') { - delete data.password; - } - mutation.mutate(data) + const onSubmit: SubmitHandler = async (data) => { + if (data.password === '') { + delete data.password } + mutation.mutate(data) + } - const onCancel = () => { - reset(); - onClose(); - } + const onCancel = () => { + reset() + onClose() + } - return ( - <> - - - - Edit User - - - - Email - - {errors.email && {errors.email.message}} - - - Full name - - - - Set Password - - {errors.password && {errors.password.message}} - - - Confirm Password - value === getValues().password || 'The passwords do not match' - })} placeholder='••••••••' type='password' /> - {errors.confirm_password && {errors.confirm_password.message}} - - - - Is superuser? - - - Is active? - - - + return ( + <> + + + + Edit User + + + + Email + + {errors.email && ( + {errors.email.message} + )} + + + Full name + + + + Set Password + + {errors.password && ( + {errors.password.message} + )} + + + Confirm Password + + value === getValues().password || + 'The passwords do not match', + })} + placeholder="Password" + type="password" + /> + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + + Is superuser? + + + + + Is active? + + + + - - - - - - - - ) + + + + + + + + ) } -export default EditUser; \ No newline at end of file +export default EditUser diff --git a/new-frontend/src/components/Common/ActionsMenu.tsx b/new-frontend/src/components/Common/ActionsMenu.tsx index 625f5d7ec7..2994dc6e74 100644 --- a/new-frontend/src/components/Common/ActionsMenu.tsx +++ b/new-frontend/src/components/Common/ActionsMenu.tsx @@ -1,42 +1,76 @@ -import React from 'react'; - -import { Button, Menu, MenuButton, MenuItem, MenuList, useDisclosure } from '@chakra-ui/react'; -import { BsThreeDotsVertical } from 'react-icons/bs'; -import { FiEdit, FiTrash } from 'react-icons/fi'; - -import EditUser from '../Admin/EditUser'; -import EditItem from '../Items/EditItem'; -import Delete from './DeleteAlert'; -import { ItemOut, UserOut } from '../../client'; +import React from 'react' +import { + Button, + Menu, + MenuButton, + MenuItem, + MenuList, + useDisclosure, +} from '@chakra-ui/react' +import { BsThreeDotsVertical } from 'react-icons/bs' +import { FiEdit, FiTrash } from 'react-icons/fi' +import EditUser from '../Admin/EditUser' +import EditItem from '../Items/EditItem' +import Delete from './DeleteAlert' +import { ItemOut, UserOut } from '../../client' interface ActionsMenuProps { - type: string; - value: ItemOut | UserOut; - disabled?: boolean; + type: string + value: ItemOut | UserOut + disabled?: boolean } const ActionsMenu: React.FC = ({ type, value, disabled }) => { - const editUserModal = useDisclosure(); - const deleteModal = useDisclosure(); + const editUserModal = useDisclosure() + const deleteModal = useDisclosure() - return ( - <> - - } variant='unstyled'> - - - }>Edit {type} - } color='ui.danger'>Delete {type} - - { - type === 'User' ? - : - } - - - - ); -}; + return ( + <> + + } + variant="unstyled" + > + + } + > + Edit {type} + + } + color="ui.danger" + > + Delete {type} + + + {type === 'User' ? ( + + ) : ( + + )} + + + + ) +} -export default ActionsMenu; +export default ActionsMenu diff --git a/new-frontend/src/components/Common/DeleteAlert.tsx b/new-frontend/src/components/Common/DeleteAlert.tsx index 6187aff1b3..b659602161 100644 --- a/new-frontend/src/components/Common/DeleteAlert.tsx +++ b/new-frontend/src/components/Common/DeleteAlert.tsx @@ -1,85 +1,116 @@ -import React from 'react'; +import React from 'react' +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Button, +} from '@chakra-ui/react' +import { useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; -import { useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ItemsService, UsersService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ItemsService, UsersService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface DeleteProps { - type: string; - id: number - isOpen: boolean; - onClose: () => void; + type: string + id: number + isOpen: boolean + onClose: () => void } const Delete: React.FC = ({ type, id, isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const cancelRef = React.useRef(null); - const { handleSubmit, formState: { isSubmitting } } = useForm(); + const queryClient = useQueryClient() + const showToast = useCustomToast() + const cancelRef = React.useRef(null) + const { + handleSubmit, + formState: { isSubmitting }, + } = useForm() - const deleteEntity = async (id: number) => { - if (type === 'Item') { - await ItemsService.deleteItem({ id: id }); - } else if (type === 'User') { - await UsersService.deleteUser({ userId: id }); - } else { - throw new Error(`Unexpected type: ${type}`); - } + const deleteEntity = async (id: number) => { + if (type === 'Item') { + await ItemsService.deleteItem({ id: id }) + } else if (type === 'User') { + await UsersService.deleteUser({ userId: id }) + } else { + throw new Error(`Unexpected type: ${type}`) } + } - const mutation = useMutation(deleteEntity, { - onSuccess: () => { - showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success'); - onClose(); - }, - onError: () => { - showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries(type === 'Item' ? 'items' : 'users'); - } - }) + const mutation = useMutation(deleteEntity, { + onSuccess: () => { + showToast( + 'Success', + `The ${type.toLowerCase()} was deleted successfully.`, + 'success', + ) + onClose() + }, + onError: () => { + showToast( + 'An error occurred.', + `An error occurred while deleting the ${type.toLowerCase()}.`, + 'error', + ) + }, + onSettled: () => { + queryClient.invalidateQueries(type === 'Item' ? 'items' : 'users') + }, + }) - const onSubmit = async () => { - mutation.mutate(id); - } + const onSubmit = async () => { + mutation.mutate(id) + } - return ( - <> - - - - - Delete {type} - + return ( + <> + + + + Delete {type} - - {type === 'User' && All items associated with this user will also be permantly deleted. } - Are you sure? You will not be able to undo this action. - + + {type === 'User' && ( + + All items associated with this user will also be{' '} + permantly deleted. + + )} + Are you sure? You will not be able to undo this action. + - - - - - - - - - ) + + + + + + + + + ) } -export default Delete; \ No newline at end of file +export default Delete diff --git a/new-frontend/src/components/Common/Navbar.tsx b/new-frontend/src/components/Common/Navbar.tsx index 109ee0072f..1a118d9765 100644 --- a/new-frontend/src/components/Common/Navbar.tsx +++ b/new-frontend/src/components/Common/Navbar.tsx @@ -1,37 +1,43 @@ -import React from 'react'; +import React from 'react' +import { Button, Flex, Icon, useDisclosure } from '@chakra-ui/react' +import { FaPlus } from 'react-icons/fa' -import { Button, Flex, Icon, useDisclosure } from '@chakra-ui/react'; -import { FaPlus } from 'react-icons/fa'; - -import AddUser from '../Admin/AddUser'; -import AddItem from '../Items/AddItem'; +import AddUser from '../Admin/AddUser' +import AddItem from '../Items/AddItem' interface NavbarProps { - type: string; + type: string } const Navbar: React.FC = ({ type }) => { - const addUserModal = useDisclosure(); - const addItemModal = useDisclosure(); + const addUserModal = useDisclosure() + const addItemModal = useDisclosure() - return ( - <> - - {/* TODO: Complete search functionality */} - {/* + return ( + <> + + {/* TODO: Complete search functionality */} + {/* */} - - - - - - ); -}; + + + + + + ) +} -export default Navbar; +export default Navbar diff --git a/new-frontend/src/components/Common/NotFound.tsx b/new-frontend/src/components/Common/NotFound.tsx index c7a78861af..e3ce2bc4c3 100644 --- a/new-frontend/src/components/Common/NotFound.tsx +++ b/new-frontend/src/components/Common/NotFound.tsx @@ -1,22 +1,42 @@ -import { Button, Container, Text } from '@chakra-ui/react'; -import { Link } from '@tanstack/react-router'; +import React from 'react' +import { Button, Container, Text } from '@chakra-ui/react' +import { Link } from '@tanstack/react-router' const NotFound: React.FC = () => { - - return ( - <> - - 404 - Oops! - Page not found. - - - - ); + return ( + <> + + + 404 + + Oops! + Page not found. + + + + ) } -export default NotFound; - - +export default NotFound diff --git a/new-frontend/src/components/Common/Sidebar.tsx b/new-frontend/src/components/Common/Sidebar.tsx index c1338dc217..3fabb5207d 100644 --- a/new-frontend/src/components/Common/Sidebar.tsx +++ b/new-frontend/src/components/Common/Sidebar.tsx @@ -1,70 +1,117 @@ -import React from 'react'; +import React from 'react' +import { + Box, + Drawer, + DrawerBody, + DrawerCloseButton, + DrawerContent, + DrawerOverlay, + Flex, + IconButton, + Image, + Text, + useColorModeValue, + useDisclosure, +} from '@chakra-ui/react' +import { FiLogOut, FiMenu } from 'react-icons/fi' +import { useQueryClient } from 'react-query' -import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react'; -import { FiLogOut, FiMenu } from 'react-icons/fi'; -import { useQueryClient } from 'react-query'; - -import Logo from '../../assets/images/fastapi-logo.svg'; -import { UserOut } from '../../client'; -import useAuth from '../../hooks/useAuth'; -import SidebarItems from './SidebarItems'; +import Logo from '../../assets/images/fastapi-logo.svg' +import { UserOut } from '../../client' +import useAuth from '../../hooks/useAuth' +import SidebarItems from './SidebarItems' const Sidebar: React.FC = () => { - const queryClient = useQueryClient(); - const bgColor = useColorModeValue('white', '#1a202c'); - const textColor = useColorModeValue('gray', 'white'); - const secBgColor = useColorModeValue('ui.secondary', '#252d3d'); - const currentUser = queryClient.getQueryData('currentUser'); - const { isOpen, onOpen, onClose } = useDisclosure(); - const { logout } = useAuth(); - - const handleLogout = async () => { - logout() - }; + const queryClient = useQueryClient() + const bgColor = useColorModeValue('white', '#1a202c') + const textColor = useColorModeValue('gray', 'white') + const secBgColor = useColorModeValue('ui.secondary', '#252d3d') + const currentUser = queryClient.getQueryData('currentUser') + const { isOpen, onOpen, onClose } = useDisclosure() + const { logout } = useAuth() + const handleLogout = async () => { + logout() + } - return ( - <> - {/* Mobile */} - } /> - - - - - - - - - - - - Log out - - - { - currentUser?.email && - Logged in as: {currentUser.email} - } - - - - - - {/* Desktop */} - - - - - - - { - currentUser?.email && - Logged in as: {currentUser.email} - } + return ( + <> + {/* Mobile */} + } + /> + + + + + + + + + + + + Log out - - - ); + + {currentUser?.email && ( + + Logged in as: {currentUser.email} + + )} + + + + + + {/* Desktop */} + + + + + + + {currentUser?.email && ( + + Logged in as: {currentUser.email} + + )} + + + + ) } -export default Sidebar; +export default Sidebar diff --git a/new-frontend/src/components/Common/SidebarItems.tsx b/new-frontend/src/components/Common/SidebarItems.tsx index d0d881957a..ae6e593cb0 100644 --- a/new-frontend/src/components/Common/SidebarItems.tsx +++ b/new-frontend/src/components/Common/SidebarItems.tsx @@ -1,60 +1,57 @@ -import React from 'react'; +import React from 'react' +import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react' +import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi' +import { Link } from '@tanstack/react-router' +import { useQueryClient } from 'react-query' -import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'; -import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi'; -import { Link } from '@tanstack/react-router'; -import { useQueryClient } from 'react-query'; - -import { UserOut } from '../../client'; +import { UserOut } from '../../client' const items = [ - { icon: FiHome, title: 'Dashboard', path: '/' }, - { icon: FiBriefcase, title: 'Items', path: '/items' }, - { icon: FiSettings, title: 'User Settings', path: '/settings' }, -]; + { icon: FiHome, title: 'Dashboard', path: '/' }, + { icon: FiBriefcase, title: 'Items', path: '/items' }, + { icon: FiSettings, title: 'User Settings', path: '/settings' }, +] interface SidebarItemsProps { - onClose?: () => void; + onClose?: () => void } const SidebarItems: React.FC = ({ onClose }) => { - const queryClient = useQueryClient(); - const textColor = useColorModeValue('ui.main', '#E2E8F0'); - const bgActive = useColorModeValue('#E2E8F0', '#4A5568'); - const currentUser = queryClient.getQueryData('currentUser'); - - - const finalItems = currentUser?.is_superuser ? [...items, { icon: FiUsers, title: 'Admin', path: '/admin' }] : items; - - const listItems = finalItems.map((item) => ( - - - {item.title} - - )); - - return ( - <> - - {listItems} - - - - ); -}; - -export default SidebarItems; + const queryClient = useQueryClient() + const textColor = useColorModeValue('ui.main', '#E2E8F0') + const bgActive = useColorModeValue('#E2E8F0', '#4A5568') + const currentUser = queryClient.getQueryData('currentUser') + + const finalItems = currentUser?.is_superuser + ? [...items, { icon: FiUsers, title: 'Admin', path: '/admin' }] + : items + + const listItems = finalItems.map((item) => ( + + + {item.title} + + )) + + return ( + <> + {listItems} + + ) +} + +export default SidebarItems diff --git a/new-frontend/src/components/Common/UserMenu.tsx b/new-frontend/src/components/Common/UserMenu.tsx index c5154a9ac7..1b2020a245 100644 --- a/new-frontend/src/components/Common/UserMenu.tsx +++ b/new-frontend/src/components/Common/UserMenu.tsx @@ -1,43 +1,59 @@ -import React from 'react'; +import React from 'react' +import { + Box, + IconButton, + Menu, + MenuButton, + MenuItem, + MenuList, +} from '@chakra-ui/react' +import { FaUserAstronaut } from 'react-icons/fa' +import { FiLogOut, FiUser } from 'react-icons/fi' -import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; -import { FaUserAstronaut } from 'react-icons/fa'; -import { FiLogOut, FiUser } from 'react-icons/fi'; - -import useAuth from '../../hooks/useAuth'; -import { Link } from '@tanstack/react-router'; +import useAuth from '../../hooks/useAuth' +import { Link } from '@tanstack/react-router' const UserMenu: React.FC = () => { - const { logout } = useAuth(); + const { logout } = useAuth() - const handleLogout = async () => { - logout() - }; + const handleLogout = async () => { + logout() + } - return ( - <> - {/* Desktop */} - - - } - bg='ui.main' - isRound - /> - - } as={Link} to='settings'> - My profile - - } onClick={handleLogout} color='ui.danger' fontWeight='bold'> - Log out - - - - - - ); -}; + return ( + <> + {/* Desktop */} + + + } + bg="ui.main" + isRound + /> + + } as={Link} to="settings"> + My profile + + } + onClick={handleLogout} + color="ui.danger" + fontWeight="bold" + > + Log out + + + + + + ) +} -export default UserMenu; +export default UserMenu diff --git a/new-frontend/src/components/Items/AddItem.tsx b/new-frontend/src/components/Items/AddItem.tsx index 8cb0a67223..b12f43d807 100644 --- a/new-frontend/src/components/Items/AddItem.tsx +++ b/new-frontend/src/components/Items/AddItem.tsx @@ -1,98 +1,123 @@ -import React from 'react'; +import React from 'react' +import { + Button, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { Button, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ApiError, ItemCreate, ItemsService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, ItemCreate, ItemsService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface AddItemProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean + onClose: () => void } const AddItem: React.FC = ({ isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: { - title: '', - description: '', - }, - }); + const queryClient = useQueryClient() + const showToast = useCustomToast() + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + title: '', + description: '', + }, + }) - const addItem = async (data: ItemCreate) => { - await ItemsService.createItem({ requestBody: data }) - } + const addItem = async (data: ItemCreate) => { + await ItemsService.createItem({ requestBody: data }) + } - const mutation = useMutation(addItem, { - onSuccess: () => { - showToast('Success!', 'Item created successfully.', 'success'); - reset(); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('items'); - } - }); + const mutation = useMutation(addItem, { + onSuccess: () => { + showToast('Success!', 'Item created successfully.', 'success') + reset() + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('items') + }, + }) - const onSubmit: SubmitHandler = (data) => { - mutation.mutate(data); - } + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data) + } - return ( - <> - - - - Add Item - - - - Title - - {errors.title && {errors.title.message}} - - - Description - - - + return ( + <> + + + + Add Item + + + + Title + + {errors.title && ( + {errors.title.message} + )} + + + Description + + + - - - - - - - - ); -}; + + + + + + + + ) +} -export default AddItem; +export default AddItem diff --git a/new-frontend/src/components/Items/EditItem.tsx b/new-frontend/src/components/Items/EditItem.tsx index 44f4dfb58b..94adc61d1c 100644 --- a/new-frontend/src/components/Items/EditItem.tsx +++ b/new-frontend/src/components/Items/EditItem.tsx @@ -1,87 +1,124 @@ -import React from 'react'; +import React from 'react' +import { + Button, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' -import { Button, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; - -import { useMutation, useQueryClient } from 'react-query'; -import { ApiError, ItemOut, ItemUpdate, ItemsService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { useMutation, useQueryClient } from 'react-query' +import { ApiError, ItemOut, ItemUpdate, ItemsService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface EditItemProps { - item: ItemOut; - isOpen: boolean; - onClose: () => void; + item: ItemOut + isOpen: boolean + onClose: () => void } const EditItem: React.FC = ({ item, isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const { register, handleSubmit, reset, formState: { isSubmitting, errors, isDirty } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: item - }); + const queryClient = useQueryClient() + const showToast = useCustomToast() + const { + register, + handleSubmit, + reset, + formState: { isSubmitting, errors, isDirty }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: item, + }) - const updateItem = async (data: ItemUpdate) => { - await ItemsService.updateItem({ id: item.id, requestBody: data }); - } + const updateItem = async (data: ItemUpdate) => { + await ItemsService.updateItem({ id: item.id, requestBody: data }) + } - const mutation = useMutation(updateItem, { - onSuccess: () => { - showToast('Success!', 'Item updated successfully.', 'success'); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('items'); - } - }); + const mutation = useMutation(updateItem, { + onSuccess: () => { + showToast('Success!', 'Item updated successfully.', 'success') + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('items') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - mutation.mutate(data) - } + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } - const onCancel = () => { - reset(); - onClose(); - } + const onCancel = () => { + reset() + onClose() + } - return ( - <> - + + + + Edit Item + + + + Title + + {errors.title && ( + {errors.title.message} + )} + + + Description + + + + + - - - - - - ) + Save + + + + + + + ) } -export default EditItem; \ No newline at end of file +export default EditItem diff --git a/new-frontend/src/components/UserSettings/Appearance.tsx b/new-frontend/src/components/UserSettings/Appearance.tsx index b1f8fb7052..100426c6c1 100644 --- a/new-frontend/src/components/UserSettings/Appearance.tsx +++ b/new-frontend/src/components/UserSettings/Appearance.tsx @@ -1,29 +1,39 @@ -import React from 'react'; - -import { Badge, Container, Heading, Radio, RadioGroup, Stack, useColorMode } from '@chakra-ui/react'; +import React from 'react' +import { + Badge, + Container, + Heading, + Radio, + RadioGroup, + Stack, + useColorMode, +} from '@chakra-ui/react' const Appearance: React.FC = () => { - const { colorMode, toggleColorMode } = useColorMode(); + const { colorMode, toggleColorMode } = useColorMode() - return ( - <> - - - Appearance - - - - {/* TODO: Add system default option */} - - Light modeDefault - - - Dark mode - - - - - - ); + return ( + <> + + + Appearance + + + + {/* TODO: Add system default option */} + + Light mode + + Default + + + + Dark mode + + + + + + ) } -export default Appearance; \ No newline at end of file +export default Appearance diff --git a/new-frontend/src/components/UserSettings/ChangePassword.tsx b/new-frontend/src/components/UserSettings/ChangePassword.tsx index 355d2c0cae..ca6f15c62e 100644 --- a/new-frontend/src/components/UserSettings/ChangePassword.tsx +++ b/new-frontend/src/components/UserSettings/ChangePassword.tsx @@ -1,74 +1,137 @@ -import React from 'react'; +import React from 'react' +import { + Box, + Button, + Container, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + Input, + useColorModeValue, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation } from 'react-query' -import { Box, Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import { ApiError, UpdatePassword, UsersService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, UpdatePassword, UsersService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface UpdatePasswordForm extends UpdatePassword { - confirm_password: string; + confirm_password: string } const ChangePassword: React.FC = () => { - const color = useColorModeValue('gray.700', 'white'); - const showToast = useCustomToast(); - const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all' - }); + const color = useColorModeValue('gray.700', 'white') + const showToast = useCustomToast() + const { + register, + handleSubmit, + reset, + getValues, + formState: { errors, isSubmitting }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + }) - const UpdatePassword = async (data: UpdatePassword) => { - await UsersService.updatePasswordMe({ requestBody: data }) - } + const UpdatePassword = async (data: UpdatePassword) => { + await UsersService.updatePasswordMe({ requestBody: data }) + } - const mutation = useMutation(UpdatePassword, { - onSuccess: () => { - showToast('Success!', 'Password updated.', 'success'); - reset(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } - }) + const mutation = useMutation(UpdatePassword, { + onSuccess: () => { + showToast('Success!', 'Password updated.', 'success') + reset() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - mutation.mutate(data); - } + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } - return ( - <> - - - Change Password - - - - Current password - - {errors.current_password && {errors.current_password.message}} - - - Set Password - - {errors.new_password && {errors.new_password.message}} - - - Confirm Password - value === getValues().new_password || 'The passwords do not match' - })} placeholder='Password' type='password' /> - {errors.confirm_password && {errors.confirm_password.message}} - - - - - - ); + return ( + <> + + + Change Password + + + + + Current password + + + {errors.current_password && ( + + {errors.current_password.message} + + )} + + + Set Password + + {errors.new_password && ( + {errors.new_password.message} + )} + + + Confirm Password + + value === getValues().new_password || + 'The passwords do not match', + })} + placeholder="Password" + type="password" + /> + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + + + ) } -export default ChangePassword; \ No newline at end of file +export default ChangePassword diff --git a/new-frontend/src/components/UserSettings/DeleteAccount.tsx b/new-frontend/src/components/UserSettings/DeleteAccount.tsx index 4c149d7783..a9c9a0f1f4 100644 --- a/new-frontend/src/components/UserSettings/DeleteAccount.tsx +++ b/new-frontend/src/components/UserSettings/DeleteAccount.tsx @@ -1,27 +1,42 @@ -import React from 'react'; +import React from 'react' +import { + Button, + Container, + Heading, + Text, + useDisclosure, +} from '@chakra-ui/react' -import { Button, Container, Heading, Text, useDisclosure } from '@chakra-ui/react'; - -import DeleteConfirmation from './DeleteConfirmation'; +import DeleteConfirmation from './DeleteConfirmation' const DeleteAccount: React.FC = () => { - const confirmationModal = useDisclosure(); + const confirmationModal = useDisclosure() - return ( - <> - - - Delete Account - - - Are you sure you want to delete your account? This action cannot be undone. - - - - - - ); + return ( + <> + + + Delete Account + + + Are you sure you want to delete your account? This action cannot be + undone. + + + + + + ) } -export default DeleteAccount; \ No newline at end of file +export default DeleteAccount diff --git a/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx b/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx index f7d8865989..f7ca666619 100644 --- a/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx +++ b/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -1,86 +1,105 @@ -import React from 'react'; - -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; -import { useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ApiError, UserOut, UsersService } from '../../client'; -import useAuth from '../../hooks/useAuth'; -import useCustomToast from '../../hooks/useCustomToast'; +import React from 'react' +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Button, +} from '@chakra-ui/react' +import { useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' + +import { ApiError, UserOut, UsersService } from '../../client' +import useAuth from '../../hooks/useAuth' +import useCustomToast from '../../hooks/useCustomToast' interface DeleteProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean + onClose: () => void } const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const cancelRef = React.useRef(null); - const { handleSubmit, formState: { isSubmitting } } = useForm(); - const currentUser = queryClient.getQueryData('currentUser'); - const { logout } = useAuth(); - - const deleteCurrentUser = async (id: number) => { - await UsersService.deleteUser({ userId: id }); - } - - const mutation = useMutation(deleteCurrentUser, { - onSuccess: () => { - showToast('Success', 'Your account has been successfully deleted.', 'success'); - logout(); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('currentUser'); - } - }) - - - const onSubmit = async () => { - mutation.mutate(currentUser!.id); - } - - return ( - <> - - - - - Confirmation Required - - - - All your account data will be permanently deleted. If you are sure, please click 'Confirm' to proceed. - - - - - - - - - - - ) + const queryClient = useQueryClient() + const showToast = useCustomToast() + const cancelRef = React.useRef(null) + const { + handleSubmit, + formState: { isSubmitting }, + } = useForm() + const currentUser = queryClient.getQueryData('currentUser') + const { logout } = useAuth() + + const deleteCurrentUser = async (id: number) => { + await UsersService.deleteUser({ userId: id }) + } + + const mutation = useMutation(deleteCurrentUser, { + onSuccess: () => { + showToast( + 'Success', + 'Your account has been successfully deleted.', + 'success', + ) + logout() + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('currentUser') + }, + }) + + const onSubmit = async () => { + mutation.mutate(currentUser!.id) + } + + return ( + <> + + + + Confirmation Required + + + All your account data will be{' '} + permanently deleted. If you are sure, please + click 'Confirm' to proceed. + + + + + + + + + + + ) } -export default DeleteConfirmation; - - - - +export default DeleteConfirmation diff --git a/new-frontend/src/components/UserSettings/UserInformation.tsx b/new-frontend/src/components/UserSettings/UserInformation.tsx index 6404a7fe18..bbfba017ab 100644 --- a/new-frontend/src/components/UserSettings/UserInformation.tsx +++ b/new-frontend/src/components/UserSettings/UserInformation.tsx @@ -1,106 +1,147 @@ -import React, { useState } from 'react'; +import React, { useState } from 'react' +import { + Box, + Button, + Container, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + Input, + Text, + useColorModeValue, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { Box, Button, Container, Flex, FormControl, FormErrorMessage, FormLabel, Heading, Input, Text, useColorModeValue } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ApiError, UserOut, UserUpdateMe, UsersService } from '../../client'; -import useAuth from '../../hooks/useAuth'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, UserOut, UserUpdateMe, UsersService } from '../../client' +import useAuth from '../../hooks/useAuth' +import useCustomToast from '../../hooks/useCustomToast' const UserInformation: React.FC = () => { - const queryClient = useQueryClient(); - const color = useColorModeValue('gray.700', 'white'); - const showToast = useCustomToast(); - const [editMode, setEditMode] = useState(false); - const { user: currentUser } = useAuth(); - const { register, handleSubmit, reset, formState: { isSubmitting, errors, isDirty } } = useForm({ - mode: 'onBlur', criteriaMode: 'all', defaultValues: { - full_name: currentUser?.full_name, - email: currentUser?.email - } - }) + const queryClient = useQueryClient() + const color = useColorModeValue('gray.700', 'white') + const showToast = useCustomToast() + const [editMode, setEditMode] = useState(false) + const { user: currentUser } = useAuth() + const { + register, + handleSubmit, + reset, + formState: { isSubmitting, errors, isDirty }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + full_name: currentUser?.full_name, + email: currentUser?.email, + }, + }) - const toggleEditMode = () => { - setEditMode(!editMode); - }; + const toggleEditMode = () => { + setEditMode(!editMode) + } - const updateInfo = async (data: UserUpdateMe) => { - await UsersService.updateUserMe({ requestBody: data }) - } + const updateInfo = async (data: UserUpdateMe) => { + await UsersService.updateUserMe({ requestBody: data }) + } - const mutation = useMutation(updateInfo, { - onSuccess: () => { - showToast('Success!', 'User updated successfully.', 'success'); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('users'); - queryClient.invalidateQueries('currentUser'); - } - }); + const mutation = useMutation(updateInfo, { + onSuccess: () => { + showToast('Success!', 'User updated successfully.', 'success') + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('users') + queryClient.invalidateQueries('currentUser') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - mutation.mutate(data) - } + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } - const onCancel = () => { - reset(); - toggleEditMode(); - } + const onCancel = () => { + reset() + toggleEditMode() + } - return ( - <> - - - User Information - - - - Full name - { - editMode ? - : - - {currentUser?.full_name || 'N/A'} - - } - - - Email - { - editMode ? - : - - {currentUser!.email} - - } - {errors.email && {errors.email.message}} - - - - {editMode && - } - - - - - ); + return ( + <> + + + User Information + + + + + Full name + + {editMode ? ( + + ) : ( + + {currentUser?.full_name || 'N/A'} + + )} + + + + Email + + {editMode ? ( + + ) : ( + + {currentUser!.email} + + )} + {errors.email && ( + {errors.email.message} + )} + + + + {editMode && ( + + )} + + + + + ) } -export default UserInformation; \ No newline at end of file +export default UserInformation diff --git a/new-frontend/src/hooks/useAuth.ts b/new-frontend/src/hooks/useAuth.ts index 2d21948bbd..a2b344499e 100644 --- a/new-frontend/src/hooks/useAuth.ts +++ b/new-frontend/src/hooks/useAuth.ts @@ -1,33 +1,42 @@ -import { useQuery } from 'react-query'; -import { useNavigate } from '@tanstack/react-router'; +import { useQuery } from 'react-query' +import { useNavigate } from '@tanstack/react-router' -import { Body_login_login_access_token as AccessToken, LoginService, UserOut, UsersService } from '../client'; +import { + Body_login_login_access_token as AccessToken, + LoginService, + UserOut, + UsersService, +} from '../client' const isLoggedIn = () => { - return localStorage.getItem('access_token') !== null; -}; + return localStorage.getItem('access_token') !== null +} const useAuth = () => { - const navigate = useNavigate(); - const { data: user, isLoading } = useQuery('currentUser', UsersService.readUserMe, { - enabled: isLoggedIn(), - }); + const navigate = useNavigate() + const { data: user, isLoading } = useQuery( + 'currentUser', + UsersService.readUserMe, + { + enabled: isLoggedIn(), + }, + ) - const login = async (data: AccessToken) => { - const response = await LoginService.loginAccessToken({ - formData: data, - }); - localStorage.setItem('access_token', response.access_token); - navigate({ to: '/' }); - }; + const login = async (data: AccessToken) => { + const response = await LoginService.loginAccessToken({ + formData: data, + }) + localStorage.setItem('access_token', response.access_token) + navigate({ to: '/' }) + } - const logout = () => { - localStorage.removeItem('access_token'); - navigate({ to: '/login' }); - }; + const logout = () => { + localStorage.removeItem('access_token') + navigate({ to: '/login' }) + } - return { login, logout, user, isLoading }; + return { login, logout, user, isLoading } } -export { isLoggedIn }; -export default useAuth; \ No newline at end of file +export { isLoggedIn } +export default useAuth diff --git a/new-frontend/src/hooks/useCustomToast.ts b/new-frontend/src/hooks/useCustomToast.ts index 2483fd3b4c..d9f7f61ff4 100644 --- a/new-frontend/src/hooks/useCustomToast.ts +++ b/new-frontend/src/hooks/useCustomToast.ts @@ -1,21 +1,23 @@ -import { useCallback } from 'react'; - -import { useToast } from '@chakra-ui/react'; +import { useCallback } from 'react' +import { useToast } from '@chakra-ui/react' const useCustomToast = () => { - const toast = useToast(); + const toast = useToast() - const showToast = useCallback((title: string, description: string, status: 'success' | 'error') => { - toast({ - title, - description, - status, - isClosable: true, - position: 'bottom-right' - }); - }, [toast]); + const showToast = useCallback( + (title: string, description: string, status: 'success' | 'error') => { + toast({ + title, + description, + status, + isClosable: true, + position: 'bottom-right', + }) + }, + [toast], + ) - return showToast; -}; + return showToast +} -export default useCustomToast; \ No newline at end of file +export default useCustomToast diff --git a/new-frontend/src/main.tsx b/new-frontend/src/main.tsx index a7a730517d..4c80602c8f 100644 --- a/new-frontend/src/main.tsx +++ b/new-frontend/src/main.tsx @@ -1,19 +1,19 @@ -import ReactDOM from 'react-dom/client'; -import { ChakraProvider } from '@chakra-ui/react'; -import { QueryClient, QueryClientProvider } from 'react-query'; +import ReactDOM from 'react-dom/client' +import { ChakraProvider } from '@chakra-ui/react' +import { QueryClient, QueryClientProvider } from 'react-query' import { RouterProvider, createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -import { OpenAPI } from './client'; -import theme from './theme'; -import { StrictMode } from 'react'; +import { OpenAPI } from './client' +import theme from './theme' +import { StrictMode } from 'react' -OpenAPI.BASE = import.meta.env.VITE_API_URL; +OpenAPI.BASE = import.meta.env.VITE_API_URL OpenAPI.TOKEN = async () => { - return localStorage.getItem('access_token') || ''; + return localStorage.getItem('access_token') || '' } -const queryClient = new QueryClient(); +const queryClient = new QueryClient() const router = createRouter({ routeTree }) declare module '@tanstack/react-router' { @@ -29,5 +29,5 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - -); \ No newline at end of file + , +) diff --git a/new-frontend/src/routes/__root.tsx b/new-frontend/src/routes/__root.tsx index d78cf139e4..9aff46f385 100644 --- a/new-frontend/src/routes/__root.tsx +++ b/new-frontend/src/routes/__root.tsx @@ -1,13 +1,14 @@ import { createRootRoute, Outlet } from '@tanstack/react-router' import { TanStackRouterDevtools } from '@tanstack/router-devtools' + import NotFound from '../components/Common/NotFound' export const Route = createRootRoute({ - component: () => ( - <> - - - - ), - notFoundComponent: () => , -}) \ No newline at end of file + component: () => ( + <> + + + + ), + notFoundComponent: () => , +}) diff --git a/new-frontend/src/routes/_layout.tsx b/new-frontend/src/routes/_layout.tsx index d1f48e6a21..b6f85a1144 100644 --- a/new-frontend/src/routes/_layout.tsx +++ b/new-frontend/src/routes/_layout.tsx @@ -1,38 +1,37 @@ -import { Flex, Spinner } from '@chakra-ui/react'; -import { Outlet, createFileRoute, redirect } from '@tanstack/react-router'; - -import Sidebar from '../components/Common/Sidebar'; -import UserMenu from '../components/Common/UserMenu'; -import useAuth, { isLoggedIn } from '../hooks/useAuth'; +import { Flex, Spinner } from '@chakra-ui/react' +import { Outlet, createFileRoute, redirect } from '@tanstack/react-router' +import Sidebar from '../components/Common/Sidebar' +import UserMenu from '../components/Common/UserMenu' +import useAuth, { isLoggedIn } from '../hooks/useAuth' export const Route = createFileRoute('/_layout')({ - component: Layout, - beforeLoad: async () => { - if (!isLoggedIn()) { - throw redirect({ - to: '/login', - }) - } + component: Layout, + beforeLoad: async () => { + if (!isLoggedIn()) { + throw redirect({ + to: '/login', + }) } + }, }) function Layout() { - const { isLoading } = useAuth(); + const { isLoading } = useAuth() - return ( - - - {isLoading ? ( - - - - ) : ( - - )} - + return ( + + + {isLoading ? ( + + - ); -}; + ) : ( + + )} + + + ) +} -export default Layout; \ No newline at end of file +export default Layout diff --git a/new-frontend/src/routes/_layout/admin.tsx b/new-frontend/src/routes/_layout/admin.tsx index b3d6a90616..eb183aa313 100644 --- a/new-frontend/src/routes/_layout/admin.tsx +++ b/new-frontend/src/routes/_layout/admin.tsx @@ -1,82 +1,117 @@ -import { Badge, Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import { createFileRoute } from '@tanstack/react-router'; -import { useQuery, useQueryClient } from 'react-query'; +import { + Badge, + Box, + Container, + Flex, + Heading, + Spinner, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, +} from '@chakra-ui/react' +import { createFileRoute } from '@tanstack/react-router' +import { useQuery, useQueryClient } from 'react-query' -import { ApiError, UserOut, UsersService } from '../../client'; -import ActionsMenu from '../../components/Common/ActionsMenu'; -import Navbar from '../../components/Common/Navbar'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, UserOut, UsersService } from '../../client' +import ActionsMenu from '../../components/Common/ActionsMenu' +import Navbar from '../../components/Common/Navbar' +import useCustomToast from '../../hooks/useCustomToast' export const Route = createFileRoute('/_layout/admin')({ - component: Admin, + component: Admin, }) function Admin() { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const currentUser = queryClient.getQueryData('currentUser'); - const { data: users, isLoading, isError, error } = useQuery('users', () => UsersService.readUsers({})) + const queryClient = useQueryClient() + const showToast = useCustomToast() + const currentUser = queryClient.getQueryData('currentUser') + const { + data: users, + isLoading, + isError, + error, + } = useQuery('users', () => UsersService.readUsers({})) - if (isError) { - const errDetail = (error as ApiError).body?.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } + if (isError) { + const errDetail = (error as ApiError).body?.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + } - return ( - <> - {isLoading ? ( - // TODO: Add skeleton - - - - ) : ( - users && - - - User Management - - - -
ID
{item.id} {item.title}{item.description || "N/A"}{item.description || 'N/A'} - +
{user.full_name || 'N/A'}{currentUser?.id === user.id && You} {user.email} - +
{item.id} {item.title} {item.description || 'N/A'} - +
- - - - - - - - - - - {users.data.map((user) => ( - - - - - - - - ))} - -
Full nameEmailRoleStatusActions
{user.full_name || 'N/A'}{currentUser?.id === user.id && You}{user.email}{user.is_superuser ? 'Superuser' : 'User'} - - - {user.is_active ? 'Active' : 'Inactive'} - - - -
-
-
- )} - - ) + return ( + <> + {isLoading ? ( + // TODO: Add skeleton + + + + ) : ( + users && ( + + + User Management + + + + + + + + + + + + + + + {users.data.map((user) => ( + + + + + + + + ))} + +
Full nameEmailRoleStatusActions
+ {user.full_name || 'N/A'} + {currentUser?.id === user.id && ( + + You + + )} + {user.email}{user.is_superuser ? 'Superuser' : 'User'} + + + {user.is_active ? 'Active' : 'Inactive'} + + + +
+
+
+ ) + )} + + ) } -export default Admin; +export default Admin diff --git a/new-frontend/src/routes/_layout/index.tsx b/new-frontend/src/routes/_layout/index.tsx index 6901801a21..852fb37822 100644 --- a/new-frontend/src/routes/_layout/index.tsx +++ b/new-frontend/src/routes/_layout/index.tsx @@ -1,27 +1,28 @@ +import { Container, Text } from '@chakra-ui/react' +import { useQueryClient } from 'react-query' +import { createFileRoute } from '@tanstack/react-router' -import { Container, Text } from '@chakra-ui/react'; -import { useQueryClient } from 'react-query'; -import { createFileRoute } from '@tanstack/react-router'; - -import { UserOut } from '../../client'; +import { UserOut } from '../../client' export const Route = createFileRoute('/_layout/')({ - component: Dashboard, + component: Dashboard, }) function Dashboard() { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() - const currentUser = queryClient.getQueryData('currentUser'); + const currentUser = queryClient.getQueryData('currentUser') - return ( - <> - - Hi, {currentUser?.full_name || currentUser?.email} 👋🏼 - Welcome back, nice to see you again! - - - ) + return ( + <> + + + Hi, {currentUser?.full_name || currentUser?.email} 👋🏼 + + Welcome back, nice to see you again! + + + ) } -export default Dashboard; \ No newline at end of file +export default Dashboard diff --git a/new-frontend/src/routes/_layout/items.tsx b/new-frontend/src/routes/_layout/items.tsx index 7a2960911d..9022d14656 100644 --- a/new-frontend/src/routes/_layout/items.tsx +++ b/new-frontend/src/routes/_layout/items.tsx @@ -1,67 +1,91 @@ -import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import { createFileRoute } from '@tanstack/react-router'; -import { useQuery } from 'react-query'; +import { + Container, + Flex, + Heading, + Spinner, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, +} from '@chakra-ui/react' +import { createFileRoute } from '@tanstack/react-router' +import { useQuery } from 'react-query' -import { ApiError, ItemsService } from '../../client'; -import ActionsMenu from '../../components/Common/ActionsMenu'; -import Navbar from '../../components/Common/Navbar'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, ItemsService } from '../../client' +import ActionsMenu from '../../components/Common/ActionsMenu' +import Navbar from '../../components/Common/Navbar' +import useCustomToast from '../../hooks/useCustomToast' export const Route = createFileRoute('/_layout/items')({ - component: Items, + component: Items, }) function Items() { - const showToast = useCustomToast(); - const { data: items, isLoading, isError, error } = useQuery('items', () => ItemsService.readItems({})) + const showToast = useCustomToast() + const { + data: items, + isLoading, + isError, + error, + } = useQuery('items', () => ItemsService.readItems({})) - if (isError) { - const errDetail = (error as ApiError).body?.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } + if (isError) { + const errDetail = (error as ApiError).body?.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + } - return ( - <> - {isLoading ? ( - // TODO: Add skeleton - - - - ) : ( - items && - - - Items Management - - - - - - - - - - - - - - {items.data.map((item) => ( - - - - - - - ))} - -
IDTitleDescriptionActions
{item.id}{item.title}{item.description || 'N/A'} - -
-
-
- )} - - ) + return ( + <> + {isLoading ? ( + // TODO: Add skeleton + + + + ) : ( + items && ( + + + Items Management + + + + + + + + + + + + + + {items.data.map((item) => ( + + + + + + + ))} + +
IDTitleDescriptionActions
{item.id}{item.title} + {item.description || 'N/A'} + + +
+
+
+ ) + )} + + ) } -export default Items; \ No newline at end of file +export default Items diff --git a/new-frontend/src/routes/_layout/settings.tsx b/new-frontend/src/routes/_layout/settings.tsx index 14d9e6a96e..fc392a9ac4 100644 --- a/new-frontend/src/routes/_layout/settings.tsx +++ b/new-frontend/src/routes/_layout/settings.tsx @@ -1,50 +1,60 @@ -import { Container, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; -import { createFileRoute } from '@tanstack/react-router'; -import { useQueryClient } from 'react-query'; +import { + Container, + Heading, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, +} from '@chakra-ui/react' +import { createFileRoute } from '@tanstack/react-router' +import { useQueryClient } from 'react-query' -import { UserOut } from '../../client'; -import Appearance from '../../components/UserSettings/Appearance'; -import ChangePassword from '../../components/UserSettings/ChangePassword'; -import DeleteAccount from '../../components/UserSettings/DeleteAccount'; -import UserInformation from '../../components/UserSettings/UserInformation'; +import { UserOut } from '../../client' +import Appearance from '../../components/UserSettings/Appearance' +import ChangePassword from '../../components/UserSettings/ChangePassword' +import DeleteAccount from '../../components/UserSettings/DeleteAccount' +import UserInformation from '../../components/UserSettings/UserInformation' const tabsConfig = [ - { title: 'My profile', component: UserInformation }, - { title: 'Password', component: ChangePassword }, - { title: 'Appearance', component: Appearance }, - { title: 'Danger zone', component: DeleteAccount }, -]; + { title: 'My profile', component: UserInformation }, + { title: 'Password', component: ChangePassword }, + { title: 'Appearance', component: Appearance }, + { title: 'Danger zone', component: DeleteAccount }, +] export const Route = createFileRoute('/_layout/settings')({ - component: UserSettings, + component: UserSettings, }) function UserSettings() { - const queryClient = useQueryClient(); - const currentUser = queryClient.getQueryData('currentUser'); - const finalTabs = currentUser?.is_superuser ? tabsConfig.slice(0, 3) : tabsConfig; + const queryClient = useQueryClient() + const currentUser = queryClient.getQueryData('currentUser') + const finalTabs = currentUser?.is_superuser + ? tabsConfig.slice(0, 3) + : tabsConfig - return ( - - - User Settings - - - - {finalTabs.map((tab, index) => ( - {tab.title} - ))} - - - {finalTabs.map((tab, index) => ( - - - - ))} - - - - ); + return ( + + + User Settings + + + + {finalTabs.map((tab, index) => ( + {tab.title} + ))} + + + {finalTabs.map((tab, index) => ( + + + + ))} + + + + ) } -export default UserSettings; \ No newline at end of file +export default UserSettings diff --git a/new-frontend/src/routes/login.tsx b/new-frontend/src/routes/login.tsx index ce4b22931a..e4be03c846 100644 --- a/new-frontend/src/routes/login.tsx +++ b/new-frontend/src/routes/login.tsx @@ -1,14 +1,30 @@ -import React from 'react'; +import React from 'react' +import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons' +import { + Button, + Center, + Container, + FormControl, + FormErrorMessage, + Icon, + Image, + Input, + InputGroup, + InputRightElement, + Link, + useBoolean, +} from '@chakra-ui/react' +import { + Link as RouterLink, + createFileRoute, + redirect, +} from '@tanstack/react-router' +import { SubmitHandler, useForm } from 'react-hook-form' -import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'; -import { Button, Center, Container, FormControl, FormErrorMessage, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react'; -import { Link as RouterLink, createFileRoute, redirect } from '@tanstack/react-router'; -import { SubmitHandler, useForm } from 'react-hook-form'; - -import Logo from '../assets/images/fastapi-logo.svg'; -import { ApiError } from '../client'; -import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token'; -import useAuth, { isLoggedIn } from '../hooks/useAuth'; +import Logo from '../assets/images/fastapi-logo.svg' +import { ApiError } from '../client' +import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token' +import useAuth, { isLoggedIn } from '../hooks/useAuth' export const Route = createFileRoute('/login')({ component: Login, @@ -18,82 +34,111 @@ export const Route = createFileRoute('/login')({ to: '/', }) } - } + }, }) function Login() { - const [show, setShow] = useBoolean(); - const { login } = useAuth(); - const [error, setError] = React.useState(null); - const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ + const [show, setShow] = useBoolean() + const { login } = useAuth() + const [error, setError] = React.useState(null) + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ mode: 'onBlur', criteriaMode: 'all', defaultValues: { username: '', - password: '' - } - }); + password: '', + }, + }) const onSubmit: SubmitHandler = async (data) => { try { - await login(data); + await login(data) } catch (err) { - const errDetail = (err as ApiError).body.detail; + const errDetail = (err as ApiError).body.detail setError(errDetail) } - }; + } return ( <> - FastAPI logo - - - {errors.username && {errors.username.message}} + FastAPI logo + + + {errors.username && ( + {errors.username.message} + )} - + - + {show ? : } - {error && - {error} - } + {error && {error}}
- + Forgot password?
-
- ); -}; + ) +} -export default Login; +export default Login diff --git a/new-frontend/src/routes/recover-password.tsx b/new-frontend/src/routes/recover-password.tsx index f29b2ebed6..38c998d24e 100644 --- a/new-frontend/src/routes/recover-password.tsx +++ b/new-frontend/src/routes/recover-password.tsx @@ -1,13 +1,21 @@ -import { Button, Container, FormControl, FormErrorMessage, Heading, Input, Text } from '@chakra-ui/react'; -import { createFileRoute, redirect } from '@tanstack/react-router'; -import { SubmitHandler, useForm } from 'react-hook-form'; +import { + Button, + Container, + FormControl, + FormErrorMessage, + Heading, + Input, + Text, +} from '@chakra-ui/react' +import { createFileRoute, redirect } from '@tanstack/react-router' +import { SubmitHandler, useForm } from 'react-hook-form' -import { LoginService } from '../client'; -import useCustomToast from '../hooks/useCustomToast'; -import { isLoggedIn } from '../hooks/useAuth'; +import { LoginService } from '../client' +import useCustomToast from '../hooks/useCustomToast' +import { isLoggedIn } from '../hooks/useAuth' interface FormData { - email: string; + email: string } export const Route = createFileRoute('/recover-password')({ @@ -18,46 +26,73 @@ export const Route = createFileRoute('/recover-password')({ to: '/', }) } - } + }, }) function RecoverPassword() { - const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm(); - const showToast = useCustomToast(); + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm() + const showToast = useCustomToast() const onSubmit: SubmitHandler = async (data) => { await LoginService.recoverPassword({ email: data.email, - }); - showToast('Email sent.', 'We sent an email with a link to get back into your account.', 'success'); - }; + }) + showToast( + 'Email sent.', + 'We sent an email with a link to get back into your account.', + 'success', + ) + } return ( - + Password Recovery - + A password recovery email will be sent to the registered account. - - {errors.email && {errors.email.message}} + + {errors.email && ( + {errors.email.message} + )} - - ); -}; + ) +} -export default RecoverPassword; +export default RecoverPassword diff --git a/new-frontend/src/routes/reset-password.tsx b/new-frontend/src/routes/reset-password.tsx index 5be798e0f4..a39db3a08c 100644 --- a/new-frontend/src/routes/reset-password.tsx +++ b/new-frontend/src/routes/reset-password.tsx @@ -1,95 +1,134 @@ +import { + Button, + Container, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + Input, + Text, +} from '@chakra-ui/react' +import { createFileRoute, redirect } from '@tanstack/react-router' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation } from 'react-query' -import { Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, Text } from '@chakra-ui/react'; -import { createFileRoute, redirect } from '@tanstack/react-router'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import { ApiError, LoginService, NewPassword } from '../client'; -import { isLoggedIn } from '../hooks/useAuth'; -import useCustomToast from '../hooks/useCustomToast'; +import { ApiError, LoginService, NewPassword } from '../client' +import { isLoggedIn } from '../hooks/useAuth' +import useCustomToast from '../hooks/useCustomToast' interface NewPasswordForm extends NewPassword { - confirm_password: string; + confirm_password: string } export const Route = createFileRoute('/reset-password')({ - component: ResetPassword, - beforeLoad: async () => { - if (isLoggedIn()) { - throw redirect({ - to: '/', - }) - } + component: ResetPassword, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: '/', + }) } + }, }) function ResetPassword() { - const { register, handleSubmit, getValues, formState: { errors } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: { - new_password: '', - } - }); - const showToast = useCustomToast(); - - const resetPassword = async (data: NewPassword) => { - const token = new URLSearchParams(window.location.search).get('token'); - await LoginService.resetPassword({ - requestBody: { new_password: data.new_password, token: token! } - }); - } + const { + register, + handleSubmit, + getValues, + formState: { errors }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + new_password: '', + }, + }) + const showToast = useCustomToast() - const mutation = useMutation(resetPassword, { - onSuccess: () => { - showToast('Success!', 'Password updated.', 'success'); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } + const resetPassword = async (data: NewPassword) => { + const token = new URLSearchParams(window.location.search).get('token') + await LoginService.resetPassword({ + requestBody: { new_password: data.new_password, token: token! }, }) + } + const mutation = useMutation(resetPassword, { + onSuccess: () => { + showToast('Success!', 'Password updated.', 'success') + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - mutation.mutate(data); - }; + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } - return ( - - - Reset Password - - - Please enter your new password and confirm it to reset your password. - - - Set Password - - {errors.new_password && {errors.new_password.message}} - - - Confirm Password - value === getValues().new_password || 'The passwords do not match' - })} placeholder='Password' type='password' /> - {errors.confirm_password && {errors.confirm_password.message}} - - - - ); -}; + return ( + + + Reset Password + + + Please enter your new password and confirm it to reset your password. + + + Set Password + + {errors.new_password && ( + {errors.new_password.message} + )} + + + Confirm Password + + value === getValues().new_password || + 'The passwords do not match', + })} + placeholder="Password" + type="password" + /> + {errors.confirm_password && ( + {errors.confirm_password.message} + )} + + + + ) +} -export default ResetPassword; \ No newline at end of file +export default ResetPassword diff --git a/new-frontend/src/theme.tsx b/new-frontend/src/theme.tsx index b741399146..2908f7d22c 100644 --- a/new-frontend/src/theme.tsx +++ b/new-frontend/src/theme.tsx @@ -1,27 +1,27 @@ import { extendTheme } from '@chakra-ui/react' const theme = extendTheme({ - colors: { - ui: { - main: '#009688', - secondary: '#EDF2F7', - success: '#48BB78', - danger: '#E53E3E', - } + colors: { + ui: { + main: '#009688', + secondary: '#EDF2F7', + success: '#48BB78', + danger: '#E53E3E', }, - components: { - Tabs: { - variants: { - enclosed: { - tab: { - _selected: { - color: 'ui.main', - }, - }, - }, + }, + components: { + Tabs: { + variants: { + enclosed: { + tab: { + _selected: { + color: 'ui.main', }, + }, }, + }, }, -}); + }, +}) -export default theme; \ No newline at end of file +export default theme From 0af0ded8b8c9970ef0245c1b00be43f2ffdcfe0b Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 13:58:54 +0000 Subject: [PATCH 247/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 4cc305600c..1e79cf726d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -12,6 +12,7 @@ ### Features +* 🎨 Format with Prettier. PR [#646](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/646) by [@alejsdev](https://github.com/alejsdev). * ✅ Add tests to raise coverage to at least 90% and fix recover password logic. PR [#632](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/632) by [@estebanx64](https://github.com/estebanx64). * ⚙️ Add Prettier and ESLint config with pre-commit. PR [#640](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/640) by [@alejsdev](https://github.com/alejsdev). * 👷 Add coverage with Smokeshow to CI and badge. PR [#638](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/638) by [@estebanx64](https://github.com/estebanx64). From b6ec1f93d463625ec58ac9f197cdaf7fb66dde3c Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:10:28 +0100 Subject: [PATCH 248/771] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Add=20Prettier=20c?= =?UTF-8?q?onfig=20(#647)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- new-frontend/.prettierrc | 8 ++++++++ new-frontend/package-lock.json | 1 + new-frontend/package.json | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 new-frontend/.prettierrc diff --git a/new-frontend/.prettierrc b/new-frontend/.prettierrc new file mode 100644 index 0000000000..1ea2d86558 --- /dev/null +++ b/new-frontend/.prettierrc @@ -0,0 +1,8 @@ +{ + "bracketSpacing": true, + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "tabWidth": 2 +} diff --git a/new-frontend/package-lock.json b/new-frontend/package-lock.json index 13f87612bf..b9e843e576 100644 --- a/new-frontend/package-lock.json +++ b/new-frontend/package-lock.json @@ -36,6 +36,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.4", "openapi-typescript-codegen": "0.25.0", + "prettier": "3.2.5", "typescript": "^5.2.2", "vite": "^5.0.0" } diff --git a/new-frontend/package.json b/new-frontend/package.json index 11b2bd72d3..6e7a135d62 100644 --- a/new-frontend/package.json +++ b/new-frontend/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "format": "prettier --write ./src/**/*.{ts,tsx,js,jsx,json,md}", "preview": "vite preview", "generate-client": "openapi --input ./openapi.json --useOptions --useUnionTypes --output ./src/client --client axios --exportSchemas true" }, @@ -39,6 +39,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.4", "openapi-typescript-codegen": "0.25.0", + "prettier": "3.2.5", "typescript": "^5.2.2", "vite": "^5.0.0" } From 2cbb5689bcb416279a01a38b8303514144767a67 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 14:10:48 +0000 Subject: [PATCH 249/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 1e79cf726d..9f73c89e8c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -105,6 +105,7 @@ ### Internal +* ⚙️ Add Prettier config. PR [#647](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/647) by [@alejsdev](https://github.com/alejsdev). * 🔧 Update pre-commit config. PR [#645](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/645) by [@alejsdev](https://github.com/alejsdev). * 👷 Add dependabot. PR [#547](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/547) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix latest-changes GitHub Action token, strike 2. PR [#546](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/546) by [@tiangolo](https://github.com/tiangolo). From 366c6f3ad378f4b8750cf003dcac49a03ff8a652 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:26:29 +0100 Subject: [PATCH 250/771] Ignore client folder (#648) --- new-frontend/.prettierignore | 1 + new-frontend/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 new-frontend/.prettierignore diff --git a/new-frontend/.prettierignore b/new-frontend/.prettierignore new file mode 100644 index 0000000000..094e68d9c4 --- /dev/null +++ b/new-frontend/.prettierignore @@ -0,0 +1 @@ +src/client/ diff --git a/new-frontend/package.json b/new-frontend/package.json index 6e7a135d62..4ca32e4655 100644 --- a/new-frontend/package.json +++ b/new-frontend/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "format": "prettier --write ./src/**/*.{ts,tsx,js,jsx,json,md}", + "format": "prettier --write ./src/**/*.{ts,tsx,js,jsx,json,md} --ignore-path .prettierignore", "preview": "vite preview", "generate-client": "openapi --input ./openapi.json --useOptions --useUnionTypes --output ./src/client --client axios --exportSchemas true" }, From a1f666a98082e5465c6a8bb8741286139928d09b Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 14:26:46 +0000 Subject: [PATCH 251/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 9f73c89e8c..037db2dd2a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🙈 Add .prettierignore and include client folder. PR [#648](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/648) by [@alejsdev](https://github.com/alejsdev). * ⏪ Revert "⚙️ Add Prettier and ESLint config with pre-commit". PR [#644](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/644) by [@alejsdev](https://github.com/alejsdev). * 🐛 Fix copier to handle string vars with spaces in quotes. PR [#631](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/631) by [@estebanx64](https://github.com/estebanx64). * ⬆ Bump tiangolo/issue-manager from 0.2.0 to 0.5.0. PR [#591](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/591) by [@dependabot[bot]](https://github.com/apps/dependabot). From 8ce5fca95e484a754be8c9d6a2e2577bb18790f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 8 Mar 2024 15:44:04 +0100 Subject: [PATCH 252/771] =?UTF-8?q?=F0=9F=94=A5=20Remove=20old=20frontend?= =?UTF-8?q?=20(#649)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.override.yml | 9 - docker-compose.yml | 26 +-- frontend/.dockerignore | 1 - frontend/.env | 9 - frontend/.gitignore | 21 -- frontend/.nvmrc | 1 - frontend/Dockerfile | 30 --- frontend/README.md | 46 ----- frontend/babel.config.js | 10 - frontend/nginx-backend-not-found.conf | 9 - frontend/nginx.conf | 11 -- frontend/package.json | 74 ------- frontend/public/favicon.ico | Bin 1150 -> 0 bytes .../img/icons/android-chrome-192x192.png | Bin 9416 -> 0 bytes .../img/icons/android-chrome-512x512.png | Bin 29808 -> 0 bytes .../img/icons/apple-touch-icon-120x120.png | Bin 3369 -> 0 bytes .../img/icons/apple-touch-icon-152x152.png | Bin 4046 -> 0 bytes .../img/icons/apple-touch-icon-180x180.png | Bin 4678 -> 0 bytes .../img/icons/apple-touch-icon-60x60.png | Bin 1491 -> 0 bytes .../img/icons/apple-touch-icon-76x76.png | Bin 1823 -> 0 bytes .../public/img/icons/apple-touch-icon.png | Bin 4678 -> 0 bytes frontend/public/img/icons/favicon-16x16.png | Bin 799 -> 0 bytes frontend/public/img/icons/favicon-32x32.png | Bin 1271 -> 0 bytes .../img/icons/msapplication-icon-144x144.png | Bin 1169 -> 0 bytes frontend/public/img/icons/mstile-150x150.png | Bin 4282 -> 0 bytes .../public/img/icons/safari-pinned-tab.svg | 149 -------------- frontend/public/index.html | 21 -- frontend/public/manifest.json | 20 -- frontend/public/robots.txt | 2 - frontend/src/App.vue | 43 ----- frontend/src/api.ts | 45 ----- frontend/src/assets/logo.png | Bin 6849 -> 0 bytes frontend/src/component-hooks.ts | 8 - .../src/components/NotificationsManager.vue | 77 -------- frontend/src/components/RouterComponent.vue | 11 -- frontend/src/components/UploadButton.vue | 34 ---- frontend/src/env.ts | 14 -- frontend/src/interfaces/index.ts | 23 --- frontend/src/main.ts | 19 -- frontend/src/plugins/vee-validate.ts | 4 - frontend/src/plugins/vuetify.ts | 6 - frontend/src/registerServiceWorker.ts | 26 --- frontend/src/router.ts | 97 ---------- frontend/src/shims-tsx.d.ts | 13 -- frontend/src/shims-vue.d.ts | 4 - frontend/src/store/admin/actions.ts | 60 ------ frontend/src/store/admin/getters.ts | 18 -- frontend/src/store/admin/index.ts | 15 -- frontend/src/store/admin/mutations.ts | 20 -- frontend/src/store/admin/state.ts | 5 - frontend/src/store/index.ts | 19 -- frontend/src/store/main/actions.ts | 173 ----------------- frontend/src/store/main/getters.ts | 29 --- frontend/src/store/main/index.ts | 21 -- frontend/src/store/main/mutations.ts | 43 ----- frontend/src/store/main/state.ts | 17 -- frontend/src/store/state.ts | 5 - frontend/src/utils.ts | 5 - frontend/src/views/Login.vue | 58 ------ frontend/src/views/PasswordRecovery.vue | 52 ----- frontend/src/views/ResetPassword.vue | 84 -------- frontend/src/views/main/Dashboard.vue | 37 ---- frontend/src/views/main/Main.vue | 182 ------------------ frontend/src/views/main/Start.vue | 38 ---- frontend/src/views/main/admin/Admin.vue | 28 --- frontend/src/views/main/admin/AdminUsers.vue | 83 -------- frontend/src/views/main/admin/CreateUser.vue | 97 ---------- frontend/src/views/main/admin/EditUser.vue | 163 ---------------- .../src/views/main/profile/UserProfile.vue | 46 ----- .../views/main/profile/UserProfileEdit.vue | 97 ---------- .../main/profile/UserProfileEditPassword.vue | 86 --------- frontend/tests/unit/upload-button.spec.ts | 15 -- frontend/tsconfig.json | 41 ---- frontend/tslint.json | 19 -- frontend/vue.config.js | 35 ---- 75 files changed, 7 insertions(+), 2447 deletions(-) delete mode 100755 frontend/.dockerignore delete mode 100644 frontend/.env delete mode 100644 frontend/.gitignore delete mode 100644 frontend/.nvmrc delete mode 100644 frontend/Dockerfile delete mode 100644 frontend/README.md delete mode 100644 frontend/babel.config.js delete mode 100644 frontend/nginx-backend-not-found.conf delete mode 100644 frontend/nginx.conf delete mode 100644 frontend/package.json delete mode 100644 frontend/public/favicon.ico delete mode 100644 frontend/public/img/icons/android-chrome-192x192.png delete mode 100644 frontend/public/img/icons/android-chrome-512x512.png delete mode 100644 frontend/public/img/icons/apple-touch-icon-120x120.png delete mode 100644 frontend/public/img/icons/apple-touch-icon-152x152.png delete mode 100644 frontend/public/img/icons/apple-touch-icon-180x180.png delete mode 100644 frontend/public/img/icons/apple-touch-icon-60x60.png delete mode 100644 frontend/public/img/icons/apple-touch-icon-76x76.png delete mode 100644 frontend/public/img/icons/apple-touch-icon.png delete mode 100644 frontend/public/img/icons/favicon-16x16.png delete mode 100644 frontend/public/img/icons/favicon-32x32.png delete mode 100644 frontend/public/img/icons/msapplication-icon-144x144.png delete mode 100644 frontend/public/img/icons/mstile-150x150.png delete mode 100644 frontend/public/img/icons/safari-pinned-tab.svg delete mode 100644 frontend/public/index.html delete mode 100644 frontend/public/manifest.json delete mode 100644 frontend/public/robots.txt delete mode 100644 frontend/src/App.vue delete mode 100644 frontend/src/api.ts delete mode 100644 frontend/src/assets/logo.png delete mode 100644 frontend/src/component-hooks.ts delete mode 100644 frontend/src/components/NotificationsManager.vue delete mode 100644 frontend/src/components/RouterComponent.vue delete mode 100644 frontend/src/components/UploadButton.vue delete mode 100644 frontend/src/env.ts delete mode 100644 frontend/src/interfaces/index.ts delete mode 100644 frontend/src/main.ts delete mode 100644 frontend/src/plugins/vee-validate.ts delete mode 100644 frontend/src/plugins/vuetify.ts delete mode 100644 frontend/src/registerServiceWorker.ts delete mode 100644 frontend/src/router.ts delete mode 100644 frontend/src/shims-tsx.d.ts delete mode 100644 frontend/src/shims-vue.d.ts delete mode 100644 frontend/src/store/admin/actions.ts delete mode 100644 frontend/src/store/admin/getters.ts delete mode 100644 frontend/src/store/admin/index.ts delete mode 100644 frontend/src/store/admin/mutations.ts delete mode 100644 frontend/src/store/admin/state.ts delete mode 100644 frontend/src/store/index.ts delete mode 100644 frontend/src/store/main/actions.ts delete mode 100644 frontend/src/store/main/getters.ts delete mode 100644 frontend/src/store/main/index.ts delete mode 100644 frontend/src/store/main/mutations.ts delete mode 100644 frontend/src/store/main/state.ts delete mode 100644 frontend/src/store/state.ts delete mode 100644 frontend/src/utils.ts delete mode 100644 frontend/src/views/Login.vue delete mode 100644 frontend/src/views/PasswordRecovery.vue delete mode 100644 frontend/src/views/ResetPassword.vue delete mode 100644 frontend/src/views/main/Dashboard.vue delete mode 100644 frontend/src/views/main/Main.vue delete mode 100644 frontend/src/views/main/Start.vue delete mode 100644 frontend/src/views/main/admin/Admin.vue delete mode 100644 frontend/src/views/main/admin/AdminUsers.vue delete mode 100644 frontend/src/views/main/admin/CreateUser.vue delete mode 100644 frontend/src/views/main/admin/EditUser.vue delete mode 100644 frontend/src/views/main/profile/UserProfile.vue delete mode 100644 frontend/src/views/main/profile/UserProfileEdit.vue delete mode 100644 frontend/src/views/main/profile/UserProfileEditPassword.vue delete mode 100644 frontend/tests/unit/upload-button.spec.ts delete mode 100644 frontend/tsconfig.json delete mode 100644 frontend/tslint.json delete mode 100644 frontend/vue.config.js diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 91abca86b0..5a6de73d22 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -72,15 +72,6 @@ services: args: INSTALL_DEV: ${INSTALL_DEV-true} - frontend: - build: - context: ./frontend - args: - FRONTEND_ENV: dev - labels: - # - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`old-frontend.localhost.tiangolo.com`) - networks: traefik-public: # For local dev, don't expect an external Traefik network diff --git a/docker-compose.yml b/docker-compose.yml index bbbe66c0b4..6dc8c8d104 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.3" services: proxy: - image: traefik:v2.2 + image: traefik:v2.3 networks: - ${TRAEFIK_PUBLIC_NETWORK?Variable not set} - default @@ -167,24 +167,12 @@ services: frontend: image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' build: - context: ./frontend - args: - FRONTEND_ENV: ${FRONTEND_ENV-production} - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - -# new-frontend: -# image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' -# build: -# context: ./new-frontend - # labels: - # - traefik.enable=true - # - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - # - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) - # - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 + context: ./new-frontend + labels: + - traefik.enable=true + - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) + - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 volumes: app-db-data: diff --git a/frontend/.dockerignore b/frontend/.dockerignore deleted file mode 100755 index 3c3629e647..0000000000 --- a/frontend/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/frontend/.env b/frontend/.env deleted file mode 100644 index 5f27856b06..0000000000 --- a/frontend/.env +++ /dev/null @@ -1,9 +0,0 @@ -VUE_APP_DOMAIN_DEV=localhost -# VUE_APP_DOMAIN_DEV=local.dockertoolbox.tiangolo.com -# VUE_APP_DOMAIN_DEV=localhost.tiangolo.com -VUE_APP_DOMAIN_STAG= -VUE_APP_DOMAIN_PROD= -VUE_APP_NAME= -VUE_APP_ENV=development -# VUE_APP_ENV=staging -# VUE_APP_ENV=production diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 185e663192..0000000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -.DS_Store -node_modules -/dist - -# local env files -.env.local -.env.*.local - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Editor directories and files -.idea -.vscode -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw* diff --git a/frontend/.nvmrc b/frontend/.nvmrc deleted file mode 100644 index b460d6f2de..0000000000 --- a/frontend/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18.12.1 diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index 5980c12a3d..0000000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# Stage 0, "build-stage", based on Node.js, to build and compile the frontend -FROM node:18.12.1 as build-stage - -WORKDIR /app - -COPY package*.json /app/ - -RUN npm install - -COPY ./ /app/ - -ARG FRONTEND_ENV=production - -ENV VUE_APP_ENV=${FRONTEND_ENV} - -ENV NODE_OPTIONS="--openssl-legacy-provider" - -# Comment out the next line to disable tests -RUN npm run test:unit - -RUN npm run build - - -# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx -FROM nginx:1.15 - -COPY --from=build-stage /app/dist/ /usr/share/nginx/html - -COPY ./nginx.conf /etc/nginx/conf.d/default.conf -COPY ./nginx-backend-not-found.conf /etc/nginx/extra-conf.d/backend-not-found.conf diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index e7c2b09005..0000000000 --- a/frontend/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# frontend - -## Node Requirements -You can use either [fnm](https://github.com/Schniz/fnm) or [nvm](https://github.com/nvm-sh/nvm) to manage your Node.js versions. - -### Using nvm -If you prefer nvm, run the following command to install the recommended Node.js version: -``` -nvm install -``` - -### Using fnm -If you prefer fnm, run the following command to install the recommended Node.js version: -``` -fnm install -``` - -## Project setup -``` -npm install -``` - -### Compiles and hot-reloads for development -``` -npm run serve -``` - -### Compiles and minifies for production -``` -npm run build -``` - -### Run your tests -``` -npm run test -``` - -### Lints and fixes files -``` -npm run lint -``` - -### Run your unit tests -``` -npm run test:unit -``` diff --git a/frontend/babel.config.js b/frontend/babel.config.js deleted file mode 100644 index 07b43d2e63..0000000000 --- a/frontend/babel.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - "presets": [ - [ - "@vue/cli-plugin-babel/preset", - { - "useBuiltIns": "entry" - } - ] - ] -} diff --git a/frontend/nginx-backend-not-found.conf b/frontend/nginx-backend-not-found.conf deleted file mode 100644 index f6fea66358..0000000000 --- a/frontend/nginx-backend-not-found.conf +++ /dev/null @@ -1,9 +0,0 @@ -location /api { - return 404; -} -location /docs { - return 404; -} -location /redoc { - return 404; -} diff --git a/frontend/nginx.conf b/frontend/nginx.conf deleted file mode 100644 index fd4472f4b5..0000000000 --- a/frontend/nginx.conf +++ /dev/null @@ -1,11 +0,0 @@ -server { - listen 80; - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html =404; - } - - include /etc/nginx/extra-conf.d/*.conf; -} diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index a83c616980..0000000000 --- a/frontend/package.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "name": "frontend", - "version": "0.1.0", - "private": true, - "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "test:unit": "vue-cli-service test:unit", - "lint": "vue-cli-service lint" - }, - "dependencies": { - "@babel/polyfill": "^7.2.5", - "axios": "^0.18.0", - "core-js": "^3.4.3", - "register-service-worker": "^1.0.0", - "typesafe-vuex": "^3.1.1", - "vee-validate": "^2.1.7", - "vue": "^2.5.22", - "vue-class-component": "^6.0.0", - "vue-property-decorator": "^7.3.0", - "vue-router": "^3.0.2", - "vuetify": "^1.4.4", - "vuex": "^3.1.0" - }, - "devDependencies": { - "@types/jest": "^23.3.13", - "@vue/cli-plugin-babel": "^4.1.1", - "@vue/cli-plugin-pwa": "^4.1.1", - "@vue/cli-plugin-typescript": "^4.1.1", - "@vue/cli-plugin-unit-jest": "^4.1.1", - "@vue/cli-service": "^4.1.1", - "@vue/test-utils": "^1.0.0-beta.28", - "babel-core": "7.0.0-bridge.0", - "ts-jest": "^23.10.5", - "typescript": "^3.2.4", - "vue-cli-plugin-vuetify": "^2.0.2", - "vue-template-compiler": "^2.5.22" - }, - "postcss": { - "plugins": { - "autoprefixer": {} - } - }, - "browserslist": [ - "> 1%", - "last 2 versions", - "not ie <= 10" - ], - "jest": { - "moduleFileExtensions": [ - "js", - "jsx", - "json", - "vue", - "ts", - "tsx" - ], - "transform": { - "^.+\\.vue$": "vue-jest", - ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub", - "^.+\\.tsx?$": "ts-jest" - }, - "moduleNameMapper": { - "^@/(.*)$": "/src/$1" - }, - "snapshotSerializers": [ - "jest-serializer-vue" - ], - "testMatch": [ - "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)" - ], - "testURL": "http://localhost/" - } -} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico deleted file mode 100644 index c7b9a43c8cd16d0b434adaf513fcacb340809a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmchVOGsN$5QZm2NTI$erQpKHrdQX(jn+pVxKN`Ng)RzW5+8_2Xb@Y)Dkd6tq9V8u z3WAh^C@KZ1kA;tohzs}b3NC_*QmUXr$oP*rH(2mdT{z*(KX=aj=bX$9kqMvFRKj;Q zwI&d~A);J>5-PDega~WT5us%#Dc(Y}C4WpP?+fS;FaZ*z_CFzgiW=w{I02=q_TUz( z?=^H2uwoIK1n%|Ay21~QgjV1emYtWttJdz^L#=DjJ@Ex*9UPc*7<=rZo*_NAh4PxA zqkso~Ioa1y$e+3kIkXi29YNLi&lW}vY6C}ut4{8ou(7w=$_=$v{yJ$h?y!&bJfq*( zL_NQRF37$6e>%9erGV?p^lRFD?|5J_eupXaS;QluyrOmBT>PJhirMYb*i?(4Tf=j~?VvnUlY_ zDCVuuk3E&T9aP~Cr-0i-MaKUjf_|U!=R&t}_CfD=d${p~HH`BPaqb9aXT}UI$iGRg z>0^GlZ`vM4?;$*LhfI(RG|XK4GF+@-W*W}YJT5&2N_ZyZuaM_Ry=%PWx>r0P(Rc?> jRc4}SfGA>*agjwN{7E7DEm(*)%rSx{B0<6wBoglxJAy|R diff --git a/frontend/public/img/icons/android-chrome-192x192.png b/frontend/public/img/icons/android-chrome-192x192.png deleted file mode 100644 index b02aa64d97167ad649e496908b35f14c603d9249..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9416 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 ROru=^AazZ3N)e9H{|11FNXq~K diff --git a/frontend/public/img/icons/apple-touch-icon-152x152.png b/frontend/public/img/icons/apple-touch-icon-152x152.png deleted file mode 100644 index f24d454a2ecb8851bb893192b64ee09386d30e24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4046 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 cK^5JQ0?3d{m_4Kdj*!1;003@kYpI!lw*K!+ zMhKRlYSNuSL+q-ouM7aSXo?F560pwcq-CNH03o~pfQkixGY~?p13-Wn0BqO;fNVAZ zF!|)S7|Vk{NF8-G)qv~2Pf`233=o0%YwN2+eiA?-6k^qmdZhqBv!<=4j0pO+T`(63 zoo?u#em%?m-0Mw>^S_yuw5QQE2PFT#_0?4&G>AGq_;Xmb8?HdT=6%0r?J_^XX*`T2 z!(G zlc3{fa#U@Ti%?||!xO+_IsQy`#8YOJQBY9uWJl5Zp)E=LG&8|S8=ZZigj3oLoTrUr z>+aQV3I&HkQ`|IzORvNB{=oQQVBZC~xoD{TK;*^hbWhxa@3|egGQ+DYyX#9uUEM)= zFLRg5cRwDkxck*`N|Z|5iejjSr;W1}tUs5udSpI$@8X8bJV6&^(5>-e%oKN<@7>$b zZ&nRPKywAFe2!gqes0IL4^^#R8F$OF6{%2zCyhmelRnY2nokO=>xreBChbjT5Wv1m zPQ~PVOi;gd`_&g}SJ9sq0WqMlX}>b8$5W=^*%xS4!c;vaUT$nBmTudc4Wayb=I3BK zw=EU{#N-6&HLCN9`AghGB@GTwsf3r@ zzZ`eg9C5T34P$z*btRD8ls`g=kbMa=a)F&4Cs)7x)^ms{dxEYHn_KM`RXtks_xIZg z{GENY*@u^xD$H>W>ITjU8QbBtLP$A4$w8jUMPSwP^01j=+WUK7)#?!|*08V@Wu8x< zhY1p)B)?)U6U}5ZXk-fXOvmr}?Z!T!{~>@hrA%WrYnD#5Tz=F4JHvD$B}LbgL_|bH z-#}k6L>_Ia-7L*7Q?ZVI4p{IVaw(oL1_tk^*f(cKd@LqvmIgQpcBgJ_SnuFhJ}^DF zqW@4&!4VmE(0-L9 z%+5$Bg!X*1F3+wgmCm5Bb#Hc9bhN@5_3H2-JiEpiO6nsuUwCsW%S7w_mrp(Kt*x49 zo1IC;3M-epyT)Q&Z}sh7`Rsg%3^&!`vb)^OTf8I)*y+i6Ng>H`b=V;MJqV5wtW7iN zr9C3;KU7A?w#PZ3@{a}|DmvDL|K5%{F(|OH5k@*Y1_W$_>)QF}Sosci49C4M9xk>a zd=wQcMlNf#n2DC*#!_qTR7loEULMx5_S&5Pa+Mhwx?-pjAU5pRKiFR-I`S;bis7U( zGw(Z6*5BunN&gWpf9>ypbYGpN{<3X*x|Oh&VJi9Ckcmbx=0UZ@82w_?gYp5LU+O=H z3T3(Vy``RUW9hUs4zg;juYZ>rZrL8K=@wddAla(uh$TPiFFe`WNtG(5kK?9Fs$I&t zbc>{HE+&2o3=RtN8H~uEUXAUD2JcrA=d`)u z2^kZ0*p9Z=Jnx0GgPnifSarTJM_+PhjA%_1_p##$#e0kIcKEiVB5X)traE!E{ zf6l>R*cRwP&e)CcA(hNeuwR>1!(c!!IbkK+(J%d0@nXEjqN+5Td;Oi(SEn0mbCGLH z9}2JcMYF~tkP0s9s;LiWAD4Fsb9jFihCYO`C!sgv${}(R-jX`xXjet~!u;Hbmc5}3 z3PYyX;O=G;-<>C2pnZuyotQ4?6RTz#&APphC7kPiKhETZ?MmgY>`CQrLvHuY{^odi zxeJyy&F0I=vy8VmCymt!*bP>`b>BU7-0%GiM9hAzfo!XP-c2PC!Ua>WN(MUz9AP^Z z{dTWjT=+)Oo(peZ9hF=~UKM1)P89z8k%8*?AqR?0A=ci<_WN3H!T`rxCQeftD zE-Oul^SM)KJXAft(aX;HFze)xu7$|ucJ7})%+x#%7)Z@-$1{;!FiMs z%=)F~(d`&Kwg+|`=ty;5C@S(gJ2zOObMJA)Pu$m`-@WSfCCqyU%i5XR){`0mgfah6 zjJ9m0G)`=hc~fg2WmAw&Kj@T}4E1$6#y5QNE@p5?A$C``vj-*f(kurf$g6I!0U9RZ zOr5R4p;uNzDl(ZYStAN4GguVJ!>n zp zU^J({4P_nPz-PYFhDl^-9EA~`3Dgh>mGIB=v_91sW!ZnR_=jvlJqoTx=)KulH+` zU(e&b`#qN5k*o6V2l(yRUGQ`T1HRf945k^Cc2nAV9!qET#0tsoRs#hI{^cDFD7InJ zj}GYHON%x4#87|U>v_Zl4H|_%&0$4&`35;V%gfz5K20B#R?7c~PZli**_JQM zA!?Ll1A5EWAcx}>$xX2UEc6{_;#Q~wP zWA-QlWCnUyc&UC0=$ICjG0vWmUkc%heLd$m4G%8uy9)aKh3@fjll{ZD4Wu7Ak@yw; zh|DK*hUpBh)9|}gXk7oH$}ccl;>RBxN)Ve1W|YgoHS8Vh;(8MH>)oGgT05fW2z40P zwO1aEVOc!zAK`kT)=A8?*e-x^xh-MY?V4L+Nx^{;SJ>eabEL+5&k7!yYN2v7!$Xy| zEAJAS>w%pD?pGbST%^}9FQggO)?I(=5B3GRL|?MC)4Ltt*z(QG=DnAE-_KjLabsP$TFl*jZ%Nq48HrN2I}lA6l~0CDNQs$*eJ|jAma;q+!}w(&Rpx0=lq( zh0$zVBXEI{Q)qj%q|(SKKc1FBn|*>Jz!nM369!#?y+@9VN^GCwqq|=%i2cTZZm2&z zWm!1fJtOQ%1Mb0vd6q_Rk}5_48p5UKfEJt;S6YrDySowlAhA=~MuLHl3Rr)!Q6ob` zEUs1L@1`E|T<-+Iq@6vxdDCWoMWS}Lgs#`&?JQqiKf@8^UU-%iZ{t#qy!y!L_9 zK#A8VP)L;yei!>KQaZbaCmL~_TI-Y(rB_7N{+M0>0glhyshUk;-`zK6U>s7%Sf60( zEp0w^c%&|10dqh{s_=_pG5U&9_7_B2+V$H#l|cyv4_PDCkQb^>THQ-~rS$YfDRs+Hq6W z?@80gKV*s@UEk?q!xw!E7gI9$U{yM6XXuRXd|oo}`bIcSBM&*E#OF5i~xQYeH`FsmnLuBL^I_UhyuH#I?0pJ$NHx#@Y( z`+B`<(uN_7GhK4SC)lxGjy-6?(v-Ba9(_E}a_S-ZT;&mFLO}hDv|=X2(VCa4$nRMY z&m9i417mV1D@HKk)=5�MrHbYR_buu=L>f*wO=Er8~{qfkYT+HoHag8)VqJoC-#@ zNT})4xRE%sP-B9?xmL5!2Y8VLa?yj@Y@r1C)6o~9GF9nWG!SI2VJ$>ejtUE%Qv&r_ zYLgZ0h3iIrw7{TyurC(g^$XT%PP5`FB3aFpP&fM-8!J#3I_ujG?;7(Xv5ni%BjYP4 z^+oydy=)_&bdxYJx`X%G5PFkUB%3h6RiOs^F?#ojsB%DbfhqzGhuv4)S1%vw@PDyE z3xWF{Y^0>+=Wg! z{>z@$rD6ej*V- z^$zKIOWOfdN4~7@ndhru01+3cxwD&_Muyja({*O;-5VnF(e$WDALNStE{1{F#ts+VR{o zSlQ*@U=Du8fgKW$lU~)O)b6FS?H1SSnKVIC=DOvbo8IUvM zVf+U4YxG%qLGd%ew7Jp8>@U`ew+A#Q=2oJHNH@_f!WadS20$KZEZ+BHAvU@FB zzRCYN5r+Z#g`C^hrH$yW7ABstwjvMM*CHGzY41bbo2zy$6E8DAOPn zUnHG?e7?u7pQ}Kqbu1jEd+=mcGea4aL6V4g(m*b7x}z|ijsjb3e|tf-3&^JK{=sWz z8sqIu9jy)#J}n}cXqtXmkb-NDVPcAKn=G9uX42zYvufu_OCr>xJ=oTNp8WA5wEqvM z{r~m8;7_C>U68iRr@|rd7UYhON#(-S_+Uf>s)hhPU{{exlxc&eD diff --git a/frontend/public/img/icons/apple-touch-icon-60x60.png b/frontend/public/img/icons/apple-touch-icon-60x60.png deleted file mode 100644 index cf10a5602e653bb126332934e2b7f34081c19a01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1491 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

aZW9cb|x>1jZDoAu$NpeWiVk@{x{! zmmBmDgx|qNN?LyIrl9$qmj{KjZ8zpa=4edK3FuFS$KFl}bWPoO*Ax#Z1c2#3JAN(O zYhi>`%H9fSnLkAq|5vs>+?U*bdfMc6TmDu+QqnN9xgfZsG25($^;v7U1nBl9Q^o*i z)&?UaYZ=~7XZa75xPKB1s~MB1m<*dN08pqiy*q!)_Kc|;EZs3&nNdMQ5#LFu+J z?*($vO)$hkMOVc16f-ep;K@_~&Y6t?0t9I0(lBkZBfic|u)3pK$Xv=A@jUQnB2sC` zbI)L9m;VNkWOao}2c3b4_u4+bg zuf-@d1$?asdD1~mORQ02#!&1Xm#41*xz}wETNqKb;kueQ*~OpcAXTpJFYUYcKoA&Z z8Ke=2Vh~cu_uTb&6AH75L!k8zo~&wRsDLs^3YkD2!~;%u6sFrzV2A5(FQ|3}+0-K- zWX)W2yq%?^)iKhyMGPB5*DnWBaLqAHLJ47;r5nvkeq3u_?J{Xaf_}6I+xpLwKS7NC z|KYU%zup)8i85jc)^Yt*I0W8;+zHUI3V7@o;3DUYasdq>DFK%im4J)F?;|B-<=}8R z>HESGa5)KyCnGII|E+YX&h4Y+xCRvyX@oGhhG4 j58+{W$RJv~NLLKlV4)8v^7hU(R-4l(}$teLl` diff --git a/frontend/public/img/icons/favicon-16x16.png b/frontend/public/img/icons/favicon-16x16.png deleted file mode 100644 index 42af00963d81b8e39a30435c60ac482d1f8756e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 799 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>WRE8{w#)hawXn|-Xp4{E;v!=;4B^%-x&;Cm1 zP>^*#n_J!T^1SBMI!C4h-R53dN8`?ylD}d{L%(vZvUKT)~-CgWFQy3lt zIqW5#zOL*K8HL%o&D;R|TePl5?VWhq^wrj^qed%lKKkpp-FogeyEi+p zE?K8rW7E1fuEJ{5jaaAp0~aIt+keS?T)@vXM=*X}V#VGMCm1~v-+0wr{w3CJ-R8wG zS@XVpzqP-5Mf0H?y-zh=XVL>S6E;rKnDmrMQlmoKbK9p$evXN`oe{6g>lvi)-+#c) zb+Al&$zRqtWk1@VTt6MPmq9d7^!kmXZn8k{sFt`!l%ynwlArU1(iRB6fMfqu& zIjIUIl?AB^nFS@u3=9=>9)IHDC=AokIOTu(jOWuJ24-b$y<~1-Wnu5hBFw@HE)6D! wQ<#-EhbWxBaplC3Ge=~Ou%B-5Sm33{@Jd{;RG<|Mp00i_>zopr0DGh}-~a#s diff --git a/frontend/public/img/icons/favicon-32x32.png b/frontend/public/img/icons/favicon-32x32.png deleted file mode 100644 index 46ca04dee251a4fa85a2891a145fbe20cc619d96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1271 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 - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - diff --git a/frontend/public/index.html b/frontend/public/index.html deleted file mode 100644 index cad5aa7efa..0000000000 --- a/frontend/public/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - <%= VUE_APP_NAME %> - - - - - - - -

- - - diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json deleted file mode 100644 index 8ce10b9116..0000000000 --- a/frontend/public/manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "frontend", - "short_name": "frontend", - "icons": [ - { - "src": "/img/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/img/icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "start_url": "/", - "display": "standalone", - "background_color": "#000000", - "theme_color": "#4DBA87" -} diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt deleted file mode 100644 index eb0536286f..0000000000 --- a/frontend/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: diff --git a/frontend/src/App.vue b/frontend/src/App.vue deleted file mode 100644 index 795a97c955..0000000000 --- a/frontend/src/App.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/frontend/src/api.ts b/frontend/src/api.ts deleted file mode 100644 index c24712b872..0000000000 --- a/frontend/src/api.ts +++ /dev/null @@ -1,45 +0,0 @@ -import axios from 'axios'; -import { apiUrl } from '@/env'; -import { IUserProfile, IUserProfileUpdate, IUserProfileCreate } from './interfaces'; - -function authHeaders(token: string) { - return { - headers: { - Authorization: `Bearer ${token}`, - }, - }; -} - -export const api = { - async logInGetToken(username: string, password: string) { - const params = new URLSearchParams(); - params.append('username', username); - params.append('password', password); - - return axios.post(`${apiUrl}/api/v1/login/access-token`, params); - }, - async getMe(token: string) { - return axios.get(`${apiUrl}/api/v1/users/me`, authHeaders(token)); - }, - async updateMe(token: string, data: IUserProfileUpdate) { - return axios.put(`${apiUrl}/api/v1/users/me`, data, authHeaders(token)); - }, - async getUsers(token: string) { - return axios.get(`${apiUrl}/api/v1/users/`, authHeaders(token)); - }, - async updateUser(token: string, userId: number, data: IUserProfileUpdate) { - return axios.put(`${apiUrl}/api/v1/users/${userId}`, data, authHeaders(token)); - }, - async createUser(token: string, data: IUserProfileCreate) { - return axios.post(`${apiUrl}/api/v1/users/`, data, authHeaders(token)); - }, - async passwordRecovery(email: string) { - return axios.post(`${apiUrl}/api/v1/password-recovery/${email}`); - }, - async resetPassword(password: string, token: string) { - return axios.post(`${apiUrl}/api/v1/reset-password/`, { - new_password: password, - token, - }); - }, -}; diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png deleted file mode 100644 index f3d2503fc2a44b5053b0837ebea6e87a2d339a43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6849 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 -
- - {{ currentNotificationContent }} - Close - -
- - diff --git a/frontend/src/components/RouterComponent.vue b/frontend/src/components/RouterComponent.vue deleted file mode 100644 index ed986a6fda..0000000000 --- a/frontend/src/components/RouterComponent.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/frontend/src/components/UploadButton.vue b/frontend/src/components/UploadButton.vue deleted file mode 100644 index 8902e949e9..0000000000 --- a/frontend/src/components/UploadButton.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/frontend/src/env.ts b/frontend/src/env.ts deleted file mode 100644 index b3387e69bc..0000000000 --- a/frontend/src/env.ts +++ /dev/null @@ -1,14 +0,0 @@ -const env = process.env.VUE_APP_ENV; - -let envApiUrl = ''; - -if (env === 'production') { - envApiUrl = `https://${process.env.VUE_APP_DOMAIN_PROD}`; -} else if (env === 'staging') { - envApiUrl = `https://${process.env.VUE_APP_DOMAIN_STAG}`; -} else { - envApiUrl = `http://${process.env.VUE_APP_DOMAIN_DEV}`; -} - -export const apiUrl = envApiUrl; -export const appName = process.env.VUE_APP_NAME; diff --git a/frontend/src/interfaces/index.ts b/frontend/src/interfaces/index.ts deleted file mode 100644 index a1b93403cf..0000000000 --- a/frontend/src/interfaces/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface IUserProfile { - email: string; - is_active: boolean; - is_superuser: boolean; - full_name: string; - id: number; -} - -export interface IUserProfileUpdate { - email?: string; - full_name?: string; - password?: string; - is_active?: boolean; - is_superuser?: boolean; -} - -export interface IUserProfileCreate { - email: string; - full_name?: string; - password?: string; - is_active?: boolean; - is_superuser?: boolean; -} diff --git a/frontend/src/main.ts b/frontend/src/main.ts deleted file mode 100644 index a844b1eab8..0000000000 --- a/frontend/src/main.ts +++ /dev/null @@ -1,19 +0,0 @@ -import '@babel/polyfill'; -// Import Component hooks before component definitions -import './component-hooks'; -import Vue from 'vue'; -import './plugins/vuetify'; -import './plugins/vee-validate'; -import App from './App.vue'; -import router from './router'; -import store from '@/store'; -import './registerServiceWorker'; -import 'vuetify/dist/vuetify.min.css'; - -Vue.config.productionTip = false; - -new Vue({ - router, - store, - render: (h) => h(App), -}).$mount('#app'); diff --git a/frontend/src/plugins/vee-validate.ts b/frontend/src/plugins/vee-validate.ts deleted file mode 100644 index 9c4238f2f7..0000000000 --- a/frontend/src/plugins/vee-validate.ts +++ /dev/null @@ -1,4 +0,0 @@ -import Vue from 'vue'; -import VeeValidate from 'vee-validate'; - -Vue.use(VeeValidate); diff --git a/frontend/src/plugins/vuetify.ts b/frontend/src/plugins/vuetify.ts deleted file mode 100644 index 8fdfce3a4a..0000000000 --- a/frontend/src/plugins/vuetify.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Vue from 'vue'; -import Vuetify from 'vuetify'; - -Vue.use(Vuetify, { - iconfont: 'md', -}); diff --git a/frontend/src/registerServiceWorker.ts b/frontend/src/registerServiceWorker.ts deleted file mode 100644 index d3db583898..0000000000 --- a/frontend/src/registerServiceWorker.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* tslint:disable:no-console */ - -import { register } from 'register-service-worker'; - -if (process.env.NODE_ENV === 'production') { - register(`${process.env.BASE_URL}service-worker.js`, { - ready() { - console.log( - 'App is being served from cache by a service worker.\n' + - 'For more details, visit https://goo.gl/AFskqB', - ); - }, - cached() { - console.log('Content has been cached for offline use.'); - }, - updated() { - console.log('New content is available; please refresh.'); - }, - offline() { - console.log('No internet connection found. App is running in offline mode.'); - }, - error(error) { - console.error('Error during service worker registration:', error); - }, - }); -} diff --git a/frontend/src/router.ts b/frontend/src/router.ts deleted file mode 100644 index b649c173ad..0000000000 --- a/frontend/src/router.ts +++ /dev/null @@ -1,97 +0,0 @@ -import Vue from 'vue'; -import Router from 'vue-router'; - -import RouterComponent from './components/RouterComponent.vue'; - -Vue.use(Router); - -export default new Router({ - mode: 'history', - base: process.env.BASE_URL, - routes: [ - { - path: '/', - component: () => import(/* webpackChunkName: "start" */ './views/main/Start.vue'), - children: [ - { - path: 'login', - // route level code-splitting - // this generates a separate chunk (about.[hash].js) for this route - // which is lazy-loaded when the route is visited. - component: () => import(/* webpackChunkName: "login" */ './views/Login.vue'), - }, - { - path: 'recover-password', - component: () => import(/* webpackChunkName: "recover-password" */ './views/PasswordRecovery.vue'), - }, - { - path: 'reset-password', - component: () => import(/* webpackChunkName: "reset-password" */ './views/ResetPassword.vue'), - }, - { - path: 'main', - component: () => import(/* webpackChunkName: "main" */ './views/main/Main.vue'), - children: [ - { - path: 'dashboard', - component: () => import(/* webpackChunkName: "main-dashboard" */ './views/main/Dashboard.vue'), - }, - { - path: 'profile', - component: RouterComponent, - redirect: 'profile/view', - children: [ - { - path: 'view', - component: () => import( - /* webpackChunkName: "main-profile" */ './views/main/profile/UserProfile.vue'), - }, - { - path: 'edit', - component: () => import( - /* webpackChunkName: "main-profile-edit" */ './views/main/profile/UserProfileEdit.vue'), - }, - { - path: 'password', - component: () => import( - /* webpackChunkName: "main-profile-password" */ './views/main/profile/UserProfileEditPassword.vue'), - }, - ], - }, - { - path: 'admin', - component: () => import(/* webpackChunkName: "main-admin" */ './views/main/admin/Admin.vue'), - redirect: 'admin/users/all', - children: [ - { - path: 'users', - redirect: 'users/all', - }, - { - path: 'users/all', - component: () => import( - /* webpackChunkName: "main-admin-users" */ './views/main/admin/AdminUsers.vue'), - }, - { - path: 'users/edit/:id', - name: 'main-admin-users-edit', - component: () => import( - /* webpackChunkName: "main-admin-users-edit" */ './views/main/admin/EditUser.vue'), - }, - { - path: 'users/create', - name: 'main-admin-users-create', - component: () => import( - /* webpackChunkName: "main-admin-users-create" */ './views/main/admin/CreateUser.vue'), - }, - ], - }, - ], - }, - ], - }, - { - path: '/*', redirect: '/', - }, - ], -}); diff --git a/frontend/src/shims-tsx.d.ts b/frontend/src/shims-tsx.d.ts deleted file mode 100644 index 3b88b58292..0000000000 --- a/frontend/src/shims-tsx.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Vue, { VNode } from 'vue'; - -declare global { - namespace JSX { - // tslint:disable no-empty-interface - interface Element extends VNode {} - // tslint:disable no-empty-interface - interface ElementClass extends Vue {} - interface IntrinsicElements { - [elem: string]: any; - } - } -} diff --git a/frontend/src/shims-vue.d.ts b/frontend/src/shims-vue.d.ts deleted file mode 100644 index 8f6f410263..0000000000 --- a/frontend/src/shims-vue.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.vue' { - import Vue from 'vue'; - export default Vue; -} diff --git a/frontend/src/store/admin/actions.ts b/frontend/src/store/admin/actions.ts deleted file mode 100644 index 125a08e6fd..0000000000 --- a/frontend/src/store/admin/actions.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { api } from '@/api'; -import { ActionContext } from 'vuex'; -import { IUserProfileCreate, IUserProfileUpdate } from '@/interfaces'; -import { State } from '../state'; -import { AdminState } from './state'; -import { getStoreAccessors } from 'typesafe-vuex'; -import { commitSetUsers, commitSetUser } from './mutations'; -import { dispatchCheckApiError } from '../main/actions'; -import { commitAddNotification, commitRemoveNotification } from '../main/mutations'; - -type MainContext = ActionContext; - -export const actions = { - async actionGetUsers(context: MainContext) { - try { - const response = await api.getUsers(context.rootState.main.token); - if (response) { - commitSetUsers(context, response.data); - } - } catch (error) { - await dispatchCheckApiError(context, error); - } - }, - async actionUpdateUser(context: MainContext, payload: { id: number, user: IUserProfileUpdate }) { - try { - const loadingNotification = { content: 'saving', showProgress: true }; - commitAddNotification(context, loadingNotification); - const response = (await Promise.all([ - api.updateUser(context.rootState.main.token, payload.id, payload.user), - await new Promise((resolve, reject) => setTimeout(() => resolve(), 500)), - ]))[0]; - commitSetUser(context, response.data); - commitRemoveNotification(context, loadingNotification); - commitAddNotification(context, { content: 'User successfully updated', color: 'success' }); - } catch (error) { - await dispatchCheckApiError(context, error); - } - }, - async actionCreateUser(context: MainContext, payload: IUserProfileCreate) { - try { - const loadingNotification = { content: 'saving', showProgress: true }; - commitAddNotification(context, loadingNotification); - const response = (await Promise.all([ - api.createUser(context.rootState.main.token, payload), - await new Promise((resolve, reject) => setTimeout(() => resolve(), 500)), - ]))[0]; - commitSetUser(context, response.data); - commitRemoveNotification(context, loadingNotification); - commitAddNotification(context, { content: 'User successfully created', color: 'success' }); - } catch (error) { - await dispatchCheckApiError(context, error); - } - }, -}; - -const { dispatch } = getStoreAccessors(''); - -export const dispatchCreateUser = dispatch(actions.actionCreateUser); -export const dispatchGetUsers = dispatch(actions.actionGetUsers); -export const dispatchUpdateUser = dispatch(actions.actionUpdateUser); diff --git a/frontend/src/store/admin/getters.ts b/frontend/src/store/admin/getters.ts deleted file mode 100644 index c5832ef449..0000000000 --- a/frontend/src/store/admin/getters.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AdminState } from './state'; -import { getStoreAccessors } from 'typesafe-vuex'; -import { State } from '../state'; - -export const getters = { - adminUsers: (state: AdminState) => state.users, - adminOneUser: (state: AdminState) => (userId: number) => { - const filteredUsers = state.users.filter((user) => user.id === userId); - if (filteredUsers.length > 0) { - return { ...filteredUsers[0] }; - } - }, -}; - -const { read } = getStoreAccessors(''); - -export const readAdminOneUser = read(getters.adminOneUser); -export const readAdminUsers = read(getters.adminUsers); diff --git a/frontend/src/store/admin/index.ts b/frontend/src/store/admin/index.ts deleted file mode 100644 index dcaf6abbd1..0000000000 --- a/frontend/src/store/admin/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { mutations } from './mutations'; -import { getters } from './getters'; -import { actions } from './actions'; -import { AdminState } from './state'; - -const defaultState: AdminState = { - users: [], -}; - -export const adminModule = { - state: defaultState, - mutations, - actions, - getters, -}; diff --git a/frontend/src/store/admin/mutations.ts b/frontend/src/store/admin/mutations.ts deleted file mode 100644 index dea471d4e7..0000000000 --- a/frontend/src/store/admin/mutations.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { IUserProfile } from '@/interfaces'; -import { AdminState } from './state'; -import { getStoreAccessors } from 'typesafe-vuex'; -import { State } from '../state'; - -export const mutations = { - setUsers(state: AdminState, payload: IUserProfile[]) { - state.users = payload; - }, - setUser(state: AdminState, payload: IUserProfile) { - const users = state.users.filter((user: IUserProfile) => user.id !== payload.id); - users.push(payload); - state.users = users; - }, -}; - -const { commit } = getStoreAccessors(''); - -export const commitSetUser = commit(mutations.setUser); -export const commitSetUsers = commit(mutations.setUsers); diff --git a/frontend/src/store/admin/state.ts b/frontend/src/store/admin/state.ts deleted file mode 100644 index 8dfefe2f99..0000000000 --- a/frontend/src/store/admin/state.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { IUserProfile } from '@/interfaces'; - -export interface AdminState { - users: IUserProfile[]; -} diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts deleted file mode 100644 index 1089971525..0000000000 --- a/frontend/src/store/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import Vue from 'vue'; -import Vuex, { StoreOptions } from 'vuex'; - -import { mainModule } from './main'; -import { State } from './state'; -import { adminModule } from './admin'; - -Vue.use(Vuex); - -const storeOptions: StoreOptions = { - modules: { - main: mainModule, - admin: adminModule, - }, -}; - -export const store = new Vuex.Store(storeOptions); - -export default store; diff --git a/frontend/src/store/main/actions.ts b/frontend/src/store/main/actions.ts deleted file mode 100644 index d02c06d53c..0000000000 --- a/frontend/src/store/main/actions.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { api } from '@/api'; -import router from '@/router'; -import { getLocalToken, removeLocalToken, saveLocalToken } from '@/utils'; -import { AxiosError } from 'axios'; -import { getStoreAccessors } from 'typesafe-vuex'; -import { ActionContext } from 'vuex'; -import { State } from '../state'; -import { - commitAddNotification, - commitRemoveNotification, - commitSetLoggedIn, - commitSetLogInError, - commitSetToken, - commitSetUserProfile, -} from './mutations'; -import { AppNotification, MainState } from './state'; - -type MainContext = ActionContext; - -export const actions = { - async actionLogIn(context: MainContext, payload: { username: string; password: string }) { - try { - const response = await api.logInGetToken(payload.username, payload.password); - const token = response.data.access_token; - if (token) { - saveLocalToken(token); - commitSetToken(context, token); - commitSetLoggedIn(context, true); - commitSetLogInError(context, false); - await dispatchGetUserProfile(context); - await dispatchRouteLoggedIn(context); - commitAddNotification(context, { content: 'Logged in', color: 'success' }); - } else { - await dispatchLogOut(context); - } - } catch (err) { - commitSetLogInError(context, true); - await dispatchLogOut(context); - } - }, - async actionGetUserProfile(context: MainContext) { - try { - const response = await api.getMe(context.state.token); - if (response.data) { - commitSetUserProfile(context, response.data); - } - } catch (error) { - await dispatchCheckApiError(context, error); - } - }, - async actionUpdateUserProfile(context: MainContext, payload) { - try { - const loadingNotification = { content: 'saving', showProgress: true }; - commitAddNotification(context, loadingNotification); - const response = (await Promise.all([ - api.updateMe(context.state.token, payload), - await new Promise((resolve, reject) => setTimeout(() => resolve(), 500)), - ]))[0]; - commitSetUserProfile(context, response.data); - commitRemoveNotification(context, loadingNotification); - commitAddNotification(context, { content: 'Profile successfully updated', color: 'success' }); - } catch (error) { - await dispatchCheckApiError(context, error); - } - }, - async actionCheckLoggedIn(context: MainContext) { - if (!context.state.isLoggedIn) { - let token = context.state.token; - if (!token) { - const localToken = getLocalToken(); - if (localToken) { - commitSetToken(context, localToken); - token = localToken; - } - } - if (token) { - try { - const response = await api.getMe(token); - commitSetLoggedIn(context, true); - commitSetUserProfile(context, response.data); - } catch (error) { - await dispatchRemoveLogIn(context); - } - } else { - await dispatchRemoveLogIn(context); - } - } - }, - async actionRemoveLogIn(context: MainContext) { - removeLocalToken(); - commitSetToken(context, ''); - commitSetLoggedIn(context, false); - }, - async actionLogOut(context: MainContext) { - await dispatchRemoveLogIn(context); - await dispatchRouteLogOut(context); - }, - async actionUserLogOut(context: MainContext) { - await dispatchLogOut(context); - commitAddNotification(context, { content: 'Logged out', color: 'success' }); - }, - actionRouteLogOut(context: MainContext) { - if (router.currentRoute.path !== '/login') { - router.push('/login'); - } - }, - async actionCheckApiError(context: MainContext, payload: AxiosError) { - if (payload.response!.status === 401) { - await dispatchLogOut(context); - } - }, - actionRouteLoggedIn(context: MainContext) { - if (router.currentRoute.path === '/login' || router.currentRoute.path === '/') { - router.push('/main'); - } - }, - async removeNotification(context: MainContext, payload: { notification: AppNotification, timeout: number }) { - return new Promise((resolve, reject) => { - setTimeout(() => { - commitRemoveNotification(context, payload.notification); - resolve(true); - }, payload.timeout); - }); - }, - async passwordRecovery(context: MainContext, payload: { username: string }) { - const loadingNotification = { content: 'Sending password recovery email', showProgress: true }; - try { - commitAddNotification(context, loadingNotification); - const response = (await Promise.all([ - api.passwordRecovery(payload.username), - await new Promise((resolve, reject) => setTimeout(() => resolve(), 500)), - ]))[0]; - commitRemoveNotification(context, loadingNotification); - commitAddNotification(context, { content: 'Password recovery email sent', color: 'success' }); - await dispatchLogOut(context); - } catch (error) { - commitRemoveNotification(context, loadingNotification); - commitAddNotification(context, { color: 'error', content: 'Incorrect username' }); - } - }, - async resetPassword(context: MainContext, payload: { password: string, token: string }) { - const loadingNotification = { content: 'Resetting password', showProgress: true }; - try { - commitAddNotification(context, loadingNotification); - const response = (await Promise.all([ - api.resetPassword(payload.password, payload.token), - await new Promise((resolve, reject) => setTimeout(() => resolve(), 500)), - ]))[0]; - commitRemoveNotification(context, loadingNotification); - commitAddNotification(context, { content: 'Password successfully reset', color: 'success' }); - await dispatchLogOut(context); - } catch (error) { - commitRemoveNotification(context, loadingNotification); - commitAddNotification(context, { color: 'error', content: 'Error resetting password' }); - } - }, -}; - -const { dispatch } = getStoreAccessors(''); - -export const dispatchCheckApiError = dispatch(actions.actionCheckApiError); -export const dispatchCheckLoggedIn = dispatch(actions.actionCheckLoggedIn); -export const dispatchGetUserProfile = dispatch(actions.actionGetUserProfile); -export const dispatchLogIn = dispatch(actions.actionLogIn); -export const dispatchLogOut = dispatch(actions.actionLogOut); -export const dispatchUserLogOut = dispatch(actions.actionUserLogOut); -export const dispatchRemoveLogIn = dispatch(actions.actionRemoveLogIn); -export const dispatchRouteLoggedIn = dispatch(actions.actionRouteLoggedIn); -export const dispatchRouteLogOut = dispatch(actions.actionRouteLogOut); -export const dispatchUpdateUserProfile = dispatch(actions.actionUpdateUserProfile); -export const dispatchRemoveNotification = dispatch(actions.removeNotification); -export const dispatchPasswordRecovery = dispatch(actions.passwordRecovery); -export const dispatchResetPassword = dispatch(actions.resetPassword); diff --git a/frontend/src/store/main/getters.ts b/frontend/src/store/main/getters.ts deleted file mode 100644 index 58f83978fa..0000000000 --- a/frontend/src/store/main/getters.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { MainState } from './state'; -import { getStoreAccessors } from 'typesafe-vuex'; -import { State } from '../state'; - -export const getters = { - hasAdminAccess: (state: MainState) => { - return ( - state.userProfile && - state.userProfile.is_superuser && state.userProfile.is_active); - }, - loginError: (state: MainState) => state.logInError, - dashboardShowDrawer: (state: MainState) => state.dashboardShowDrawer, - dashboardMiniDrawer: (state: MainState) => state.dashboardMiniDrawer, - userProfile: (state: MainState) => state.userProfile, - token: (state: MainState) => state.token, - isLoggedIn: (state: MainState) => state.isLoggedIn, - firstNotification: (state: MainState) => state.notifications.length > 0 && state.notifications[0], -}; - -const {read} = getStoreAccessors(''); - -export const readDashboardMiniDrawer = read(getters.dashboardMiniDrawer); -export const readDashboardShowDrawer = read(getters.dashboardShowDrawer); -export const readHasAdminAccess = read(getters.hasAdminAccess); -export const readIsLoggedIn = read(getters.isLoggedIn); -export const readLoginError = read(getters.loginError); -export const readToken = read(getters.token); -export const readUserProfile = read(getters.userProfile); -export const readFirstNotification = read(getters.firstNotification); diff --git a/frontend/src/store/main/index.ts b/frontend/src/store/main/index.ts deleted file mode 100644 index 56ba1a0c2f..0000000000 --- a/frontend/src/store/main/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { mutations } from './mutations'; -import { getters } from './getters'; -import { actions } from './actions'; -import { MainState } from './state'; - -const defaultState: MainState = { - isLoggedIn: null, - token: '', - logInError: false, - userProfile: null, - dashboardMiniDrawer: false, - dashboardShowDrawer: true, - notifications: [], -}; - -export const mainModule = { - state: defaultState, - mutations, - actions, - getters, -}; diff --git a/frontend/src/store/main/mutations.ts b/frontend/src/store/main/mutations.ts deleted file mode 100644 index 3e9c8ba2c0..0000000000 --- a/frontend/src/store/main/mutations.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { IUserProfile } from '@/interfaces'; -import { MainState, AppNotification } from './state'; -import { getStoreAccessors } from 'typesafe-vuex'; -import { State } from '../state'; - - -export const mutations = { - setToken(state: MainState, payload: string) { - state.token = payload; - }, - setLoggedIn(state: MainState, payload: boolean) { - state.isLoggedIn = payload; - }, - setLogInError(state: MainState, payload: boolean) { - state.logInError = payload; - }, - setUserProfile(state: MainState, payload: IUserProfile) { - state.userProfile = payload; - }, - setDashboardMiniDrawer(state: MainState, payload: boolean) { - state.dashboardMiniDrawer = payload; - }, - setDashboardShowDrawer(state: MainState, payload: boolean) { - state.dashboardShowDrawer = payload; - }, - addNotification(state: MainState, payload: AppNotification) { - state.notifications.push(payload); - }, - removeNotification(state: MainState, payload: AppNotification) { - state.notifications = state.notifications.filter((notification) => notification !== payload); - }, -}; - -const {commit} = getStoreAccessors(''); - -export const commitSetDashboardMiniDrawer = commit(mutations.setDashboardMiniDrawer); -export const commitSetDashboardShowDrawer = commit(mutations.setDashboardShowDrawer); -export const commitSetLoggedIn = commit(mutations.setLoggedIn); -export const commitSetLogInError = commit(mutations.setLogInError); -export const commitSetToken = commit(mutations.setToken); -export const commitSetUserProfile = commit(mutations.setUserProfile); -export const commitAddNotification = commit(mutations.addNotification); -export const commitRemoveNotification = commit(mutations.removeNotification); diff --git a/frontend/src/store/main/state.ts b/frontend/src/store/main/state.ts deleted file mode 100644 index be24b63ae9..0000000000 --- a/frontend/src/store/main/state.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { IUserProfile } from '@/interfaces'; - -export interface AppNotification { - content: string; - color?: string; - showProgress?: boolean; -} - -export interface MainState { - token: string; - isLoggedIn: boolean | null; - logInError: boolean; - userProfile: IUserProfile | null; - dashboardMiniDrawer: boolean; - dashboardShowDrawer: boolean; - notifications: AppNotification[]; -} diff --git a/frontend/src/store/state.ts b/frontend/src/store/state.ts deleted file mode 100644 index ecec111cd8..0000000000 --- a/frontend/src/store/state.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { MainState } from './main/state'; - -export interface State { - main: MainState; -} diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts deleted file mode 100644 index ade11b6a2e..0000000000 --- a/frontend/src/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const getLocalToken = () => localStorage.getItem('token'); - -export const saveLocalToken = (token: string) => localStorage.setItem('token', token); - -export const removeLocalToken = () => localStorage.removeItem('token'); diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue deleted file mode 100644 index 28bcb5965e..0000000000 --- a/frontend/src/views/Login.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - - - diff --git a/frontend/src/views/PasswordRecovery.vue b/frontend/src/views/PasswordRecovery.vue deleted file mode 100644 index bc1a7ade3f..0000000000 --- a/frontend/src/views/PasswordRecovery.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - diff --git a/frontend/src/views/ResetPassword.vue b/frontend/src/views/ResetPassword.vue deleted file mode 100644 index 3e680eb171..0000000000 --- a/frontend/src/views/ResetPassword.vue +++ /dev/null @@ -1,84 +0,0 @@ - - - diff --git a/frontend/src/views/main/Dashboard.vue b/frontend/src/views/main/Dashboard.vue deleted file mode 100644 index 421879b1b6..0000000000 --- a/frontend/src/views/main/Dashboard.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - diff --git a/frontend/src/views/main/Main.vue b/frontend/src/views/main/Main.vue deleted file mode 100644 index 846d93bd40..0000000000 --- a/frontend/src/views/main/Main.vue +++ /dev/null @@ -1,182 +0,0 @@ - - - diff --git a/frontend/src/views/main/Start.vue b/frontend/src/views/main/Start.vue deleted file mode 100644 index 71eeaafeff..0000000000 --- a/frontend/src/views/main/Start.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/frontend/src/views/main/admin/Admin.vue b/frontend/src/views/main/admin/Admin.vue deleted file mode 100644 index 1282176aaf..0000000000 --- a/frontend/src/views/main/admin/Admin.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/frontend/src/views/main/admin/AdminUsers.vue b/frontend/src/views/main/admin/AdminUsers.vue deleted file mode 100644 index 9b35d9a6c1..0000000000 --- a/frontend/src/views/main/admin/AdminUsers.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - diff --git a/frontend/src/views/main/admin/CreateUser.vue b/frontend/src/views/main/admin/CreateUser.vue deleted file mode 100644 index 892283ec6c..0000000000 --- a/frontend/src/views/main/admin/CreateUser.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - diff --git a/frontend/src/views/main/admin/EditUser.vue b/frontend/src/views/main/admin/EditUser.vue deleted file mode 100644 index 7421233140..0000000000 --- a/frontend/src/views/main/admin/EditUser.vue +++ /dev/null @@ -1,163 +0,0 @@ - - - diff --git a/frontend/src/views/main/profile/UserProfile.vue b/frontend/src/views/main/profile/UserProfile.vue deleted file mode 100644 index 25960bd42e..0000000000 --- a/frontend/src/views/main/profile/UserProfile.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - diff --git a/frontend/src/views/main/profile/UserProfileEdit.vue b/frontend/src/views/main/profile/UserProfileEdit.vue deleted file mode 100644 index dfbea8d874..0000000000 --- a/frontend/src/views/main/profile/UserProfileEdit.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - diff --git a/frontend/src/views/main/profile/UserProfileEditPassword.vue b/frontend/src/views/main/profile/UserProfileEditPassword.vue deleted file mode 100644 index be69392a82..0000000000 --- a/frontend/src/views/main/profile/UserProfileEditPassword.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/frontend/tests/unit/upload-button.spec.ts b/frontend/tests/unit/upload-button.spec.ts deleted file mode 100644 index b40eed7bea..0000000000 --- a/frontend/tests/unit/upload-button.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import UploadButton from '@/components/UploadButton.vue'; -import '@/plugins/vuetify'; - -describe('UploadButton.vue', () => { - it('renders props.title when passed', () => { - const title = 'upload a file'; - const wrapper = shallowMount(UploadButton, { - slots: { - default: title, - }, - }); - expect(wrapper.text()).toMatch(title); - }); -}); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json deleted file mode 100644 index 88cfbc31d8..0000000000 --- a/frontend/tsconfig.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "compilerOptions": { - "noImplicitAny": false, - "target": "esnext", - "module": "esnext", - "strict": true, - "jsx": "preserve", - "importHelpers": true, - "moduleResolution": "node", - "experimentalDecorators": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "sourceMap": true, - "baseUrl": ".", - "types": [ - "webpack-env", - "jest" - ], - "paths": { - "@/*": [ - "src/*" - ] - }, - "lib": [ - "esnext", - "dom", - "dom.iterable", - "scripthost" - ] - }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.vue", - "tests/**/*.ts", - "tests/**/*.tsx" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/frontend/tslint.json b/frontend/tslint.json deleted file mode 100644 index 2b37e401c3..0000000000 --- a/frontend/tslint.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "defaultSeverity": "warning", - "extends": [ - "tslint:recommended" - ], - "linterOptions": { - "exclude": [ - "node_modules/**" - ] - }, - "rules": { - "quotemark": [true, "single"], - "indent": [true, "spaces", 2], - "interface-name": false, - "ordered-imports": false, - "object-literal-sort-keys": false, - "no-consecutive-blank-lines": false - } -} diff --git a/frontend/vue.config.js b/frontend/vue.config.js deleted file mode 100644 index 140713412f..0000000000 --- a/frontend/vue.config.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - // Fix Vuex-typescript in prod: https://github.com/istrib/vuex-typescript/issues/13#issuecomment-409869231 - configureWebpack: (config) => { - if (process.env.NODE_ENV === 'production') { - config.optimization.minimizer[0].options.terserOptions = Object.assign( - {}, - config.optimization.minimizer[0].options.terserOptions, - { - ecma: 5, - compress: { - keep_fnames: true, - }, - warnings: false, - mangle: { - keep_fnames: true, - }, - }, - ); - } - }, - chainWebpack: config => { - config.module - .rule('vue') - .use('vue-loader') - .loader('vue-loader') - .tap(options => Object.assign(options, { - transformAssetUrls: { - 'v-img': ['src', 'lazy-src'], - 'v-card': 'src', - 'v-card-media': 'src', - 'v-responsive': 'src', - } - })); - }, -} From 5c4195c4b28d2293d99213be52ace9f30fd3878f Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 14:44:21 +0000 Subject: [PATCH 253/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 037db2dd2a..2657485610 100644 --- a/release-notes.md +++ b/release-notes.md @@ -60,6 +60,7 @@ ### Refactors +* 🔥 Remove old frontend. PR [#649](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/649) by [@tiangolo](https://github.com/tiangolo). * ♻ Move project source files to top level from src, update Sentry dependency. PR [#630](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/630) by [@estebanx64](https://github.com/estebanx64). * ♻ Refactor Python folder tree. PR [#629](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/629) by [@estebanx64](https://github.com/estebanx64). * ♻️ Refactor old CRUD utils and tests. PR [#622](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/622) by [@alejsdev](https://github.com/alejsdev). From 20cda64335c28f1bfee611dd220f3276ae4a7596 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:43:21 +0100 Subject: [PATCH 254/771] =?UTF-8?q?=F0=9F=90=9B=20Fix=20but=20when=20editi?= =?UTF-8?q?ng=20own=20user=20(#651)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- new-frontend/src/components/UserSettings/UserInformation.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/new-frontend/src/components/UserSettings/UserInformation.tsx b/new-frontend/src/components/UserSettings/UserInformation.tsx index bbfba017ab..7d75f71676 100644 --- a/new-frontend/src/components/UserSettings/UserInformation.tsx +++ b/new-frontend/src/components/UserSettings/UserInformation.tsx @@ -29,6 +29,7 @@ const UserInformation: React.FC = () => { register, handleSubmit, reset, + getValues, formState: { isSubmitting, errors, isDirty }, } = useForm({ mode: 'onBlur', @@ -128,7 +129,7 @@ const UserInformation: React.FC = () => { onClick={toggleEditMode} type={editMode ? 'button' : 'submit'} isLoading={editMode ? isSubmitting : false} - isDisabled={editMode ? !isDirty : false} + isDisabled={editMode ? !isDirty || !getValues('email') : false} > {editMode ? 'Save' : 'Edit'} From 1833e849c535d4ebed0e77e962ff7dbf46cd04e5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 16:43:41 +0000 Subject: [PATCH 255/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 2657485610..816bef53c6 100644 --- a/release-notes.md +++ b/release-notes.md @@ -52,6 +52,7 @@ ### Fixes +* 🐛 Fix bug when editing own user. PR [#651](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/651) by [@alejsdev](https://github.com/alejsdev). * 🐛 Add `onClose` to `SidebarItems`. PR [#589](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/589) by [@alejsdev](https://github.com/alejsdev). * 🐛 Fix positional argument bug in `init_db.py`. PR [#562](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/562) by [@alejsdev](https://github.com/alejsdev). * 📌 Fix flower Docker image, pin version. PR [#396](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/396) by [@sanggusti](https://github.com/sanggusti). From 6bf12f572a7c01b6ea667d4df78454570314f052 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:45:34 +0100 Subject: [PATCH 256/771] =?UTF-8?q?=F0=9F=94=A7=20Add=20script=20for=20ESL?= =?UTF-8?q?int?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- new-frontend/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/new-frontend/package.json b/new-frontend/package.json index 4ca32e4655..0f1846fdc1 100644 --- a/new-frontend/package.json +++ b/new-frontend/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", + "lint": "eslint ./src --ext .ts,.tsx --fix", "format": "prettier --write ./src/**/*.{ts,tsx,js,jsx,json,md} --ignore-path .prettierignore", "preview": "vite preview", "generate-client": "openapi --input ./openapi.json --useOptions --useUnionTypes --output ./src/client --client axios --exportSchemas true" From 1706d65e2ba4b5dcce1c9546788e8aa92333da11 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 16:45:50 +0000 Subject: [PATCH 257/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 816bef53c6..fb387cf62d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -108,6 +108,7 @@ ### Internal +* 🔧 Add script for ESLint. PR [#650](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/650) by [@alejsdev](https://github.com/alejsdev). * ⚙️ Add Prettier config. PR [#647](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/647) by [@alejsdev](https://github.com/alejsdev). * 🔧 Update pre-commit config. PR [#645](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/645) by [@alejsdev](https://github.com/alejsdev). * 👷 Add dependabot. PR [#547](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/547) by [@tiangolo](https://github.com/tiangolo). From 0245332a1f206ec75e352f69f5b2d54762bdc085 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:23:54 +0100 Subject: [PATCH 258/771] =?UTF-8?q?=F0=9F=9A=9A=20Move=20new-frontend=20to?= =?UTF-8?q?=20frontend=20(#652)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 2 +- {new-frontend => frontend}/.dockerignore | 2 +- {new-frontend => frontend}/.env | 0 {new-frontend => frontend}/.eslintrc.cjs | 0 {new-frontend => frontend}/.gitignore | 0 {new-frontend => frontend}/.prettierignore | 0 {new-frontend => frontend}/.prettierrc | 0 {new-frontend => frontend}/Dockerfile | 2 +- {new-frontend => frontend}/README.md | 0 {new-frontend => frontend}/index.html | 0 .../modify-openapi-operationids.js | 0 .../nginx-backend-not-found.conf | 0 {new-frontend => frontend}/nginx.conf | 4 ++-- {new-frontend => frontend}/package-lock.json | 4 ++-- {new-frontend => frontend}/package.json | 2 +- .../src/assets/images/fastapi-logo.svg | 0 .../src/assets/images/favicon.png | Bin .../src/client/core/ApiError.ts | 0 .../src/client/core/ApiRequestOptions.ts | 0 .../src/client/core/ApiResult.ts | 0 .../src/client/core/CancelablePromise.ts | 0 .../src/client/core/OpenAPI.ts | 0 .../src/client/core/request.ts | 0 {new-frontend => frontend}/src/client/index.ts | 0 .../client/models/Body_login_login_access_token.ts | 0 .../src/client/models/HTTPValidationError.ts | 0 .../src/client/models/ItemCreate.ts | 0 .../src/client/models/ItemOut.ts | 0 .../src/client/models/ItemUpdate.ts | 0 .../src/client/models/ItemsOut.ts | 0 .../src/client/models/Message.ts | 0 .../src/client/models/NewPassword.ts | 0 .../src/client/models/Token.ts | 0 .../src/client/models/UpdatePassword.ts | 0 .../src/client/models/UserCreate.ts | 0 .../src/client/models/UserCreateOpen.ts | 0 .../src/client/models/UserOut.ts | 0 .../src/client/models/UserUpdate.ts | 0 .../src/client/models/UserUpdateMe.ts | 0 .../src/client/models/UsersOut.ts | 0 .../src/client/models/ValidationError.ts | 0 .../schemas/$Body_login_login_access_token.ts | 0 .../src/client/schemas/$HTTPValidationError.ts | 0 .../src/client/schemas/$ItemCreate.ts | 0 .../src/client/schemas/$ItemOut.ts | 0 .../src/client/schemas/$ItemUpdate.ts | 0 .../src/client/schemas/$ItemsOut.ts | 0 .../src/client/schemas/$Message.ts | 0 .../src/client/schemas/$NewPassword.ts | 0 .../src/client/schemas/$Token.ts | 0 .../src/client/schemas/$UpdatePassword.ts | 0 .../src/client/schemas/$UserCreate.ts | 0 .../src/client/schemas/$UserCreateOpen.ts | 0 .../src/client/schemas/$UserOut.ts | 0 .../src/client/schemas/$UserUpdate.ts | 0 .../src/client/schemas/$UserUpdateMe.ts | 0 .../src/client/schemas/$UsersOut.ts | 0 .../src/client/schemas/$ValidationError.ts | 0 .../src/client/services/ItemsService.ts | 0 .../src/client/services/LoginService.ts | 0 .../src/client/services/UsersService.ts | 0 .../src/client/services/UtilsService.ts | 0 .../src/components/Admin/AddUser.tsx | 0 .../src/components/Admin/EditUser.tsx | 0 .../src/components/Common/ActionsMenu.tsx | 0 .../src/components/Common/DeleteAlert.tsx | 0 .../src/components/Common/Navbar.tsx | 0 .../src/components/Common/NotFound.tsx | 0 .../src/components/Common/Sidebar.tsx | 0 .../src/components/Common/SidebarItems.tsx | 0 .../src/components/Common/UserMenu.tsx | 0 .../src/components/Items/AddItem.tsx | 0 .../src/components/Items/EditItem.tsx | 0 .../src/components/UserSettings/Appearance.tsx | 0 .../src/components/UserSettings/ChangePassword.tsx | 0 .../src/components/UserSettings/DeleteAccount.tsx | 0 .../components/UserSettings/DeleteConfirmation.tsx | 0 .../src/components/UserSettings/UserInformation.tsx | 0 {new-frontend => frontend}/src/hooks/useAuth.ts | 0 .../src/hooks/useCustomToast.ts | 0 {new-frontend => frontend}/src/index.css | 0 {new-frontend => frontend}/src/main.tsx | 0 {new-frontend => frontend}/src/routeTree.gen.ts | 0 {new-frontend => frontend}/src/routes/__root.tsx | 0 {new-frontend => frontend}/src/routes/_layout.tsx | 0 .../src/routes/_layout/admin.tsx | 0 .../src/routes/_layout/index.tsx | 0 .../src/routes/_layout/items.tsx | 0 .../src/routes/_layout/settings.tsx | 0 {new-frontend => frontend}/src/routes/login.tsx | 0 .../src/routes/recover-password.tsx | 0 .../src/routes/reset-password.tsx | 0 {new-frontend => frontend}/src/theme.tsx | 0 {new-frontend => frontend}/src/vite-env.d.ts | 0 {new-frontend => frontend}/tsconfig.json | 0 {new-frontend => frontend}/tsconfig.node.json | 0 {new-frontend => frontend}/vite.config.ts | 0 97 files changed, 8 insertions(+), 8 deletions(-) rename {new-frontend => frontend}/.dockerignore (72%) rename {new-frontend => frontend}/.env (100%) rename {new-frontend => frontend}/.eslintrc.cjs (100%) rename {new-frontend => frontend}/.gitignore (100%) rename {new-frontend => frontend}/.prettierignore (100%) rename {new-frontend => frontend}/.prettierrc (100%) rename {new-frontend => frontend}/Dockerfile (94%) rename {new-frontend => frontend}/README.md (100%) rename {new-frontend => frontend}/index.html (100%) rename {new-frontend => frontend}/modify-openapi-operationids.js (100%) rename {new-frontend => frontend}/nginx-backend-not-found.conf (100%) rename {new-frontend => frontend}/nginx.conf (96%) rename {new-frontend => frontend}/package-lock.json (99%) rename {new-frontend => frontend}/package.json (98%) rename {new-frontend => frontend}/src/assets/images/fastapi-logo.svg (100%) rename {new-frontend => frontend}/src/assets/images/favicon.png (100%) rename {new-frontend => frontend}/src/client/core/ApiError.ts (100%) rename {new-frontend => frontend}/src/client/core/ApiRequestOptions.ts (100%) rename {new-frontend => frontend}/src/client/core/ApiResult.ts (100%) rename {new-frontend => frontend}/src/client/core/CancelablePromise.ts (100%) rename {new-frontend => frontend}/src/client/core/OpenAPI.ts (100%) rename {new-frontend => frontend}/src/client/core/request.ts (100%) rename {new-frontend => frontend}/src/client/index.ts (100%) rename {new-frontend => frontend}/src/client/models/Body_login_login_access_token.ts (100%) rename {new-frontend => frontend}/src/client/models/HTTPValidationError.ts (100%) rename {new-frontend => frontend}/src/client/models/ItemCreate.ts (100%) rename {new-frontend => frontend}/src/client/models/ItemOut.ts (100%) rename {new-frontend => frontend}/src/client/models/ItemUpdate.ts (100%) rename {new-frontend => frontend}/src/client/models/ItemsOut.ts (100%) rename {new-frontend => frontend}/src/client/models/Message.ts (100%) rename {new-frontend => frontend}/src/client/models/NewPassword.ts (100%) rename {new-frontend => frontend}/src/client/models/Token.ts (100%) rename {new-frontend => frontend}/src/client/models/UpdatePassword.ts (100%) rename {new-frontend => frontend}/src/client/models/UserCreate.ts (100%) rename {new-frontend => frontend}/src/client/models/UserCreateOpen.ts (100%) rename {new-frontend => frontend}/src/client/models/UserOut.ts (100%) rename {new-frontend => frontend}/src/client/models/UserUpdate.ts (100%) rename {new-frontend => frontend}/src/client/models/UserUpdateMe.ts (100%) rename {new-frontend => frontend}/src/client/models/UsersOut.ts (100%) rename {new-frontend => frontend}/src/client/models/ValidationError.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$Body_login_login_access_token.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$HTTPValidationError.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$ItemCreate.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$ItemOut.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$ItemUpdate.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$ItemsOut.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$Message.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$NewPassword.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$Token.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$UpdatePassword.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$UserCreate.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$UserCreateOpen.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$UserOut.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$UserUpdate.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$UserUpdateMe.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$UsersOut.ts (100%) rename {new-frontend => frontend}/src/client/schemas/$ValidationError.ts (100%) rename {new-frontend => frontend}/src/client/services/ItemsService.ts (100%) rename {new-frontend => frontend}/src/client/services/LoginService.ts (100%) rename {new-frontend => frontend}/src/client/services/UsersService.ts (100%) rename {new-frontend => frontend}/src/client/services/UtilsService.ts (100%) rename {new-frontend => frontend}/src/components/Admin/AddUser.tsx (100%) rename {new-frontend => frontend}/src/components/Admin/EditUser.tsx (100%) rename {new-frontend => frontend}/src/components/Common/ActionsMenu.tsx (100%) rename {new-frontend => frontend}/src/components/Common/DeleteAlert.tsx (100%) rename {new-frontend => frontend}/src/components/Common/Navbar.tsx (100%) rename {new-frontend => frontend}/src/components/Common/NotFound.tsx (100%) rename {new-frontend => frontend}/src/components/Common/Sidebar.tsx (100%) rename {new-frontend => frontend}/src/components/Common/SidebarItems.tsx (100%) rename {new-frontend => frontend}/src/components/Common/UserMenu.tsx (100%) rename {new-frontend => frontend}/src/components/Items/AddItem.tsx (100%) rename {new-frontend => frontend}/src/components/Items/EditItem.tsx (100%) rename {new-frontend => frontend}/src/components/UserSettings/Appearance.tsx (100%) rename {new-frontend => frontend}/src/components/UserSettings/ChangePassword.tsx (100%) rename {new-frontend => frontend}/src/components/UserSettings/DeleteAccount.tsx (100%) rename {new-frontend => frontend}/src/components/UserSettings/DeleteConfirmation.tsx (100%) rename {new-frontend => frontend}/src/components/UserSettings/UserInformation.tsx (100%) rename {new-frontend => frontend}/src/hooks/useAuth.ts (100%) rename {new-frontend => frontend}/src/hooks/useCustomToast.ts (100%) rename {new-frontend => frontend}/src/index.css (100%) rename {new-frontend => frontend}/src/main.tsx (100%) rename {new-frontend => frontend}/src/routeTree.gen.ts (100%) rename {new-frontend => frontend}/src/routes/__root.tsx (100%) rename {new-frontend => frontend}/src/routes/_layout.tsx (100%) rename {new-frontend => frontend}/src/routes/_layout/admin.tsx (100%) rename {new-frontend => frontend}/src/routes/_layout/index.tsx (100%) rename {new-frontend => frontend}/src/routes/_layout/items.tsx (100%) rename {new-frontend => frontend}/src/routes/_layout/settings.tsx (100%) rename {new-frontend => frontend}/src/routes/login.tsx (100%) rename {new-frontend => frontend}/src/routes/recover-password.tsx (100%) rename {new-frontend => frontend}/src/routes/reset-password.tsx (100%) rename {new-frontend => frontend}/src/theme.tsx (100%) rename {new-frontend => frontend}/src/vite-env.d.ts (100%) rename {new-frontend => frontend}/tsconfig.json (100%) rename {new-frontend => frontend}/tsconfig.node.json (100%) rename {new-frontend => frontend}/vite.config.ts (100%) diff --git a/docker-compose.yml b/docker-compose.yml index 6dc8c8d104..552fbda4e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -167,7 +167,7 @@ services: frontend: image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' build: - context: ./new-frontend + context: ./frontend labels: - traefik.enable=true - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} diff --git a/new-frontend/.dockerignore b/frontend/.dockerignore similarity index 72% rename from new-frontend/.dockerignore rename to frontend/.dockerignore index 76add878f8..f06235c460 100644 --- a/new-frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,2 +1,2 @@ node_modules -dist \ No newline at end of file +dist diff --git a/new-frontend/.env b/frontend/.env similarity index 100% rename from new-frontend/.env rename to frontend/.env diff --git a/new-frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs similarity index 100% rename from new-frontend/.eslintrc.cjs rename to frontend/.eslintrc.cjs diff --git a/new-frontend/.gitignore b/frontend/.gitignore similarity index 100% rename from new-frontend/.gitignore rename to frontend/.gitignore diff --git a/new-frontend/.prettierignore b/frontend/.prettierignore similarity index 100% rename from new-frontend/.prettierignore rename to frontend/.prettierignore diff --git a/new-frontend/.prettierrc b/frontend/.prettierrc similarity index 100% rename from new-frontend/.prettierrc rename to frontend/.prettierrc diff --git a/new-frontend/Dockerfile b/frontend/Dockerfile similarity index 94% rename from new-frontend/Dockerfile rename to frontend/Dockerfile index c676821937..3fe5f9789a 100644 --- a/new-frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,5 @@ # Stage 0, "build-stage", based on Node.js, to build and compile the frontend -FROM node:20 as build-stage +FROM node:20 as build-stage WORKDIR /app diff --git a/new-frontend/README.md b/frontend/README.md similarity index 100% rename from new-frontend/README.md rename to frontend/README.md diff --git a/new-frontend/index.html b/frontend/index.html similarity index 100% rename from new-frontend/index.html rename to frontend/index.html diff --git a/new-frontend/modify-openapi-operationids.js b/frontend/modify-openapi-operationids.js similarity index 100% rename from new-frontend/modify-openapi-operationids.js rename to frontend/modify-openapi-operationids.js diff --git a/new-frontend/nginx-backend-not-found.conf b/frontend/nginx-backend-not-found.conf similarity index 100% rename from new-frontend/nginx-backend-not-found.conf rename to frontend/nginx-backend-not-found.conf diff --git a/new-frontend/nginx.conf b/frontend/nginx.conf similarity index 96% rename from new-frontend/nginx.conf rename to frontend/nginx.conf index ed11d3aa19..fd4472f4b5 100644 --- a/new-frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,11 +1,11 @@ server { listen 80; - + location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html =404; } - + include /etc/nginx/extra-conf.d/*.conf; } diff --git a/new-frontend/package-lock.json b/frontend/package-lock.json similarity index 99% rename from new-frontend/package-lock.json rename to frontend/package-lock.json index b9e843e576..9e28762a37 100644 --- a/new-frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "new-frontend", + "name": "frontend", "version": "0.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "new-frontend", + "name": "frontend", "version": "0.0.0", "dependencies": { "@chakra-ui/icons": "2.1.1", diff --git a/new-frontend/package.json b/frontend/package.json similarity index 98% rename from new-frontend/package.json rename to frontend/package.json index 0f1846fdc1..22da180404 100644 --- a/new-frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "new-frontend", + "name": "frontend", "private": true, "version": "0.0.0", "type": "module", diff --git a/new-frontend/src/assets/images/fastapi-logo.svg b/frontend/src/assets/images/fastapi-logo.svg similarity index 100% rename from new-frontend/src/assets/images/fastapi-logo.svg rename to frontend/src/assets/images/fastapi-logo.svg diff --git a/new-frontend/src/assets/images/favicon.png b/frontend/src/assets/images/favicon.png similarity index 100% rename from new-frontend/src/assets/images/favicon.png rename to frontend/src/assets/images/favicon.png diff --git a/new-frontend/src/client/core/ApiError.ts b/frontend/src/client/core/ApiError.ts similarity index 100% rename from new-frontend/src/client/core/ApiError.ts rename to frontend/src/client/core/ApiError.ts diff --git a/new-frontend/src/client/core/ApiRequestOptions.ts b/frontend/src/client/core/ApiRequestOptions.ts similarity index 100% rename from new-frontend/src/client/core/ApiRequestOptions.ts rename to frontend/src/client/core/ApiRequestOptions.ts diff --git a/new-frontend/src/client/core/ApiResult.ts b/frontend/src/client/core/ApiResult.ts similarity index 100% rename from new-frontend/src/client/core/ApiResult.ts rename to frontend/src/client/core/ApiResult.ts diff --git a/new-frontend/src/client/core/CancelablePromise.ts b/frontend/src/client/core/CancelablePromise.ts similarity index 100% rename from new-frontend/src/client/core/CancelablePromise.ts rename to frontend/src/client/core/CancelablePromise.ts diff --git a/new-frontend/src/client/core/OpenAPI.ts b/frontend/src/client/core/OpenAPI.ts similarity index 100% rename from new-frontend/src/client/core/OpenAPI.ts rename to frontend/src/client/core/OpenAPI.ts diff --git a/new-frontend/src/client/core/request.ts b/frontend/src/client/core/request.ts similarity index 100% rename from new-frontend/src/client/core/request.ts rename to frontend/src/client/core/request.ts diff --git a/new-frontend/src/client/index.ts b/frontend/src/client/index.ts similarity index 100% rename from new-frontend/src/client/index.ts rename to frontend/src/client/index.ts diff --git a/new-frontend/src/client/models/Body_login_login_access_token.ts b/frontend/src/client/models/Body_login_login_access_token.ts similarity index 100% rename from new-frontend/src/client/models/Body_login_login_access_token.ts rename to frontend/src/client/models/Body_login_login_access_token.ts diff --git a/new-frontend/src/client/models/HTTPValidationError.ts b/frontend/src/client/models/HTTPValidationError.ts similarity index 100% rename from new-frontend/src/client/models/HTTPValidationError.ts rename to frontend/src/client/models/HTTPValidationError.ts diff --git a/new-frontend/src/client/models/ItemCreate.ts b/frontend/src/client/models/ItemCreate.ts similarity index 100% rename from new-frontend/src/client/models/ItemCreate.ts rename to frontend/src/client/models/ItemCreate.ts diff --git a/new-frontend/src/client/models/ItemOut.ts b/frontend/src/client/models/ItemOut.ts similarity index 100% rename from new-frontend/src/client/models/ItemOut.ts rename to frontend/src/client/models/ItemOut.ts diff --git a/new-frontend/src/client/models/ItemUpdate.ts b/frontend/src/client/models/ItemUpdate.ts similarity index 100% rename from new-frontend/src/client/models/ItemUpdate.ts rename to frontend/src/client/models/ItemUpdate.ts diff --git a/new-frontend/src/client/models/ItemsOut.ts b/frontend/src/client/models/ItemsOut.ts similarity index 100% rename from new-frontend/src/client/models/ItemsOut.ts rename to frontend/src/client/models/ItemsOut.ts diff --git a/new-frontend/src/client/models/Message.ts b/frontend/src/client/models/Message.ts similarity index 100% rename from new-frontend/src/client/models/Message.ts rename to frontend/src/client/models/Message.ts diff --git a/new-frontend/src/client/models/NewPassword.ts b/frontend/src/client/models/NewPassword.ts similarity index 100% rename from new-frontend/src/client/models/NewPassword.ts rename to frontend/src/client/models/NewPassword.ts diff --git a/new-frontend/src/client/models/Token.ts b/frontend/src/client/models/Token.ts similarity index 100% rename from new-frontend/src/client/models/Token.ts rename to frontend/src/client/models/Token.ts diff --git a/new-frontend/src/client/models/UpdatePassword.ts b/frontend/src/client/models/UpdatePassword.ts similarity index 100% rename from new-frontend/src/client/models/UpdatePassword.ts rename to frontend/src/client/models/UpdatePassword.ts diff --git a/new-frontend/src/client/models/UserCreate.ts b/frontend/src/client/models/UserCreate.ts similarity index 100% rename from new-frontend/src/client/models/UserCreate.ts rename to frontend/src/client/models/UserCreate.ts diff --git a/new-frontend/src/client/models/UserCreateOpen.ts b/frontend/src/client/models/UserCreateOpen.ts similarity index 100% rename from new-frontend/src/client/models/UserCreateOpen.ts rename to frontend/src/client/models/UserCreateOpen.ts diff --git a/new-frontend/src/client/models/UserOut.ts b/frontend/src/client/models/UserOut.ts similarity index 100% rename from new-frontend/src/client/models/UserOut.ts rename to frontend/src/client/models/UserOut.ts diff --git a/new-frontend/src/client/models/UserUpdate.ts b/frontend/src/client/models/UserUpdate.ts similarity index 100% rename from new-frontend/src/client/models/UserUpdate.ts rename to frontend/src/client/models/UserUpdate.ts diff --git a/new-frontend/src/client/models/UserUpdateMe.ts b/frontend/src/client/models/UserUpdateMe.ts similarity index 100% rename from new-frontend/src/client/models/UserUpdateMe.ts rename to frontend/src/client/models/UserUpdateMe.ts diff --git a/new-frontend/src/client/models/UsersOut.ts b/frontend/src/client/models/UsersOut.ts similarity index 100% rename from new-frontend/src/client/models/UsersOut.ts rename to frontend/src/client/models/UsersOut.ts diff --git a/new-frontend/src/client/models/ValidationError.ts b/frontend/src/client/models/ValidationError.ts similarity index 100% rename from new-frontend/src/client/models/ValidationError.ts rename to frontend/src/client/models/ValidationError.ts diff --git a/new-frontend/src/client/schemas/$Body_login_login_access_token.ts b/frontend/src/client/schemas/$Body_login_login_access_token.ts similarity index 100% rename from new-frontend/src/client/schemas/$Body_login_login_access_token.ts rename to frontend/src/client/schemas/$Body_login_login_access_token.ts diff --git a/new-frontend/src/client/schemas/$HTTPValidationError.ts b/frontend/src/client/schemas/$HTTPValidationError.ts similarity index 100% rename from new-frontend/src/client/schemas/$HTTPValidationError.ts rename to frontend/src/client/schemas/$HTTPValidationError.ts diff --git a/new-frontend/src/client/schemas/$ItemCreate.ts b/frontend/src/client/schemas/$ItemCreate.ts similarity index 100% rename from new-frontend/src/client/schemas/$ItemCreate.ts rename to frontend/src/client/schemas/$ItemCreate.ts diff --git a/new-frontend/src/client/schemas/$ItemOut.ts b/frontend/src/client/schemas/$ItemOut.ts similarity index 100% rename from new-frontend/src/client/schemas/$ItemOut.ts rename to frontend/src/client/schemas/$ItemOut.ts diff --git a/new-frontend/src/client/schemas/$ItemUpdate.ts b/frontend/src/client/schemas/$ItemUpdate.ts similarity index 100% rename from new-frontend/src/client/schemas/$ItemUpdate.ts rename to frontend/src/client/schemas/$ItemUpdate.ts diff --git a/new-frontend/src/client/schemas/$ItemsOut.ts b/frontend/src/client/schemas/$ItemsOut.ts similarity index 100% rename from new-frontend/src/client/schemas/$ItemsOut.ts rename to frontend/src/client/schemas/$ItemsOut.ts diff --git a/new-frontend/src/client/schemas/$Message.ts b/frontend/src/client/schemas/$Message.ts similarity index 100% rename from new-frontend/src/client/schemas/$Message.ts rename to frontend/src/client/schemas/$Message.ts diff --git a/new-frontend/src/client/schemas/$NewPassword.ts b/frontend/src/client/schemas/$NewPassword.ts similarity index 100% rename from new-frontend/src/client/schemas/$NewPassword.ts rename to frontend/src/client/schemas/$NewPassword.ts diff --git a/new-frontend/src/client/schemas/$Token.ts b/frontend/src/client/schemas/$Token.ts similarity index 100% rename from new-frontend/src/client/schemas/$Token.ts rename to frontend/src/client/schemas/$Token.ts diff --git a/new-frontend/src/client/schemas/$UpdatePassword.ts b/frontend/src/client/schemas/$UpdatePassword.ts similarity index 100% rename from new-frontend/src/client/schemas/$UpdatePassword.ts rename to frontend/src/client/schemas/$UpdatePassword.ts diff --git a/new-frontend/src/client/schemas/$UserCreate.ts b/frontend/src/client/schemas/$UserCreate.ts similarity index 100% rename from new-frontend/src/client/schemas/$UserCreate.ts rename to frontend/src/client/schemas/$UserCreate.ts diff --git a/new-frontend/src/client/schemas/$UserCreateOpen.ts b/frontend/src/client/schemas/$UserCreateOpen.ts similarity index 100% rename from new-frontend/src/client/schemas/$UserCreateOpen.ts rename to frontend/src/client/schemas/$UserCreateOpen.ts diff --git a/new-frontend/src/client/schemas/$UserOut.ts b/frontend/src/client/schemas/$UserOut.ts similarity index 100% rename from new-frontend/src/client/schemas/$UserOut.ts rename to frontend/src/client/schemas/$UserOut.ts diff --git a/new-frontend/src/client/schemas/$UserUpdate.ts b/frontend/src/client/schemas/$UserUpdate.ts similarity index 100% rename from new-frontend/src/client/schemas/$UserUpdate.ts rename to frontend/src/client/schemas/$UserUpdate.ts diff --git a/new-frontend/src/client/schemas/$UserUpdateMe.ts b/frontend/src/client/schemas/$UserUpdateMe.ts similarity index 100% rename from new-frontend/src/client/schemas/$UserUpdateMe.ts rename to frontend/src/client/schemas/$UserUpdateMe.ts diff --git a/new-frontend/src/client/schemas/$UsersOut.ts b/frontend/src/client/schemas/$UsersOut.ts similarity index 100% rename from new-frontend/src/client/schemas/$UsersOut.ts rename to frontend/src/client/schemas/$UsersOut.ts diff --git a/new-frontend/src/client/schemas/$ValidationError.ts b/frontend/src/client/schemas/$ValidationError.ts similarity index 100% rename from new-frontend/src/client/schemas/$ValidationError.ts rename to frontend/src/client/schemas/$ValidationError.ts diff --git a/new-frontend/src/client/services/ItemsService.ts b/frontend/src/client/services/ItemsService.ts similarity index 100% rename from new-frontend/src/client/services/ItemsService.ts rename to frontend/src/client/services/ItemsService.ts diff --git a/new-frontend/src/client/services/LoginService.ts b/frontend/src/client/services/LoginService.ts similarity index 100% rename from new-frontend/src/client/services/LoginService.ts rename to frontend/src/client/services/LoginService.ts diff --git a/new-frontend/src/client/services/UsersService.ts b/frontend/src/client/services/UsersService.ts similarity index 100% rename from new-frontend/src/client/services/UsersService.ts rename to frontend/src/client/services/UsersService.ts diff --git a/new-frontend/src/client/services/UtilsService.ts b/frontend/src/client/services/UtilsService.ts similarity index 100% rename from new-frontend/src/client/services/UtilsService.ts rename to frontend/src/client/services/UtilsService.ts diff --git a/new-frontend/src/components/Admin/AddUser.tsx b/frontend/src/components/Admin/AddUser.tsx similarity index 100% rename from new-frontend/src/components/Admin/AddUser.tsx rename to frontend/src/components/Admin/AddUser.tsx diff --git a/new-frontend/src/components/Admin/EditUser.tsx b/frontend/src/components/Admin/EditUser.tsx similarity index 100% rename from new-frontend/src/components/Admin/EditUser.tsx rename to frontend/src/components/Admin/EditUser.tsx diff --git a/new-frontend/src/components/Common/ActionsMenu.tsx b/frontend/src/components/Common/ActionsMenu.tsx similarity index 100% rename from new-frontend/src/components/Common/ActionsMenu.tsx rename to frontend/src/components/Common/ActionsMenu.tsx diff --git a/new-frontend/src/components/Common/DeleteAlert.tsx b/frontend/src/components/Common/DeleteAlert.tsx similarity index 100% rename from new-frontend/src/components/Common/DeleteAlert.tsx rename to frontend/src/components/Common/DeleteAlert.tsx diff --git a/new-frontend/src/components/Common/Navbar.tsx b/frontend/src/components/Common/Navbar.tsx similarity index 100% rename from new-frontend/src/components/Common/Navbar.tsx rename to frontend/src/components/Common/Navbar.tsx diff --git a/new-frontend/src/components/Common/NotFound.tsx b/frontend/src/components/Common/NotFound.tsx similarity index 100% rename from new-frontend/src/components/Common/NotFound.tsx rename to frontend/src/components/Common/NotFound.tsx diff --git a/new-frontend/src/components/Common/Sidebar.tsx b/frontend/src/components/Common/Sidebar.tsx similarity index 100% rename from new-frontend/src/components/Common/Sidebar.tsx rename to frontend/src/components/Common/Sidebar.tsx diff --git a/new-frontend/src/components/Common/SidebarItems.tsx b/frontend/src/components/Common/SidebarItems.tsx similarity index 100% rename from new-frontend/src/components/Common/SidebarItems.tsx rename to frontend/src/components/Common/SidebarItems.tsx diff --git a/new-frontend/src/components/Common/UserMenu.tsx b/frontend/src/components/Common/UserMenu.tsx similarity index 100% rename from new-frontend/src/components/Common/UserMenu.tsx rename to frontend/src/components/Common/UserMenu.tsx diff --git a/new-frontend/src/components/Items/AddItem.tsx b/frontend/src/components/Items/AddItem.tsx similarity index 100% rename from new-frontend/src/components/Items/AddItem.tsx rename to frontend/src/components/Items/AddItem.tsx diff --git a/new-frontend/src/components/Items/EditItem.tsx b/frontend/src/components/Items/EditItem.tsx similarity index 100% rename from new-frontend/src/components/Items/EditItem.tsx rename to frontend/src/components/Items/EditItem.tsx diff --git a/new-frontend/src/components/UserSettings/Appearance.tsx b/frontend/src/components/UserSettings/Appearance.tsx similarity index 100% rename from new-frontend/src/components/UserSettings/Appearance.tsx rename to frontend/src/components/UserSettings/Appearance.tsx diff --git a/new-frontend/src/components/UserSettings/ChangePassword.tsx b/frontend/src/components/UserSettings/ChangePassword.tsx similarity index 100% rename from new-frontend/src/components/UserSettings/ChangePassword.tsx rename to frontend/src/components/UserSettings/ChangePassword.tsx diff --git a/new-frontend/src/components/UserSettings/DeleteAccount.tsx b/frontend/src/components/UserSettings/DeleteAccount.tsx similarity index 100% rename from new-frontend/src/components/UserSettings/DeleteAccount.tsx rename to frontend/src/components/UserSettings/DeleteAccount.tsx diff --git a/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx b/frontend/src/components/UserSettings/DeleteConfirmation.tsx similarity index 100% rename from new-frontend/src/components/UserSettings/DeleteConfirmation.tsx rename to frontend/src/components/UserSettings/DeleteConfirmation.tsx diff --git a/new-frontend/src/components/UserSettings/UserInformation.tsx b/frontend/src/components/UserSettings/UserInformation.tsx similarity index 100% rename from new-frontend/src/components/UserSettings/UserInformation.tsx rename to frontend/src/components/UserSettings/UserInformation.tsx diff --git a/new-frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts similarity index 100% rename from new-frontend/src/hooks/useAuth.ts rename to frontend/src/hooks/useAuth.ts diff --git a/new-frontend/src/hooks/useCustomToast.ts b/frontend/src/hooks/useCustomToast.ts similarity index 100% rename from new-frontend/src/hooks/useCustomToast.ts rename to frontend/src/hooks/useCustomToast.ts diff --git a/new-frontend/src/index.css b/frontend/src/index.css similarity index 100% rename from new-frontend/src/index.css rename to frontend/src/index.css diff --git a/new-frontend/src/main.tsx b/frontend/src/main.tsx similarity index 100% rename from new-frontend/src/main.tsx rename to frontend/src/main.tsx diff --git a/new-frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts similarity index 100% rename from new-frontend/src/routeTree.gen.ts rename to frontend/src/routeTree.gen.ts diff --git a/new-frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx similarity index 100% rename from new-frontend/src/routes/__root.tsx rename to frontend/src/routes/__root.tsx diff --git a/new-frontend/src/routes/_layout.tsx b/frontend/src/routes/_layout.tsx similarity index 100% rename from new-frontend/src/routes/_layout.tsx rename to frontend/src/routes/_layout.tsx diff --git a/new-frontend/src/routes/_layout/admin.tsx b/frontend/src/routes/_layout/admin.tsx similarity index 100% rename from new-frontend/src/routes/_layout/admin.tsx rename to frontend/src/routes/_layout/admin.tsx diff --git a/new-frontend/src/routes/_layout/index.tsx b/frontend/src/routes/_layout/index.tsx similarity index 100% rename from new-frontend/src/routes/_layout/index.tsx rename to frontend/src/routes/_layout/index.tsx diff --git a/new-frontend/src/routes/_layout/items.tsx b/frontend/src/routes/_layout/items.tsx similarity index 100% rename from new-frontend/src/routes/_layout/items.tsx rename to frontend/src/routes/_layout/items.tsx diff --git a/new-frontend/src/routes/_layout/settings.tsx b/frontend/src/routes/_layout/settings.tsx similarity index 100% rename from new-frontend/src/routes/_layout/settings.tsx rename to frontend/src/routes/_layout/settings.tsx diff --git a/new-frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx similarity index 100% rename from new-frontend/src/routes/login.tsx rename to frontend/src/routes/login.tsx diff --git a/new-frontend/src/routes/recover-password.tsx b/frontend/src/routes/recover-password.tsx similarity index 100% rename from new-frontend/src/routes/recover-password.tsx rename to frontend/src/routes/recover-password.tsx diff --git a/new-frontend/src/routes/reset-password.tsx b/frontend/src/routes/reset-password.tsx similarity index 100% rename from new-frontend/src/routes/reset-password.tsx rename to frontend/src/routes/reset-password.tsx diff --git a/new-frontend/src/theme.tsx b/frontend/src/theme.tsx similarity index 100% rename from new-frontend/src/theme.tsx rename to frontend/src/theme.tsx diff --git a/new-frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts similarity index 100% rename from new-frontend/src/vite-env.d.ts rename to frontend/src/vite-env.d.ts diff --git a/new-frontend/tsconfig.json b/frontend/tsconfig.json similarity index 100% rename from new-frontend/tsconfig.json rename to frontend/tsconfig.json diff --git a/new-frontend/tsconfig.node.json b/frontend/tsconfig.node.json similarity index 100% rename from new-frontend/tsconfig.node.json rename to frontend/tsconfig.node.json diff --git a/new-frontend/vite.config.ts b/frontend/vite.config.ts similarity index 100% rename from new-frontend/vite.config.ts rename to frontend/vite.config.ts From e51d015010447b476f4b8c1d579367ff2cbcce91 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 18:24:16 +0000 Subject: [PATCH 259/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index fb387cf62d..faedcb18f3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -108,6 +108,7 @@ ### Internal +* 🚚 Move new-frontend to frontend. PR [#652](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/652) by [@alejsdev](https://github.com/alejsdev). * 🔧 Add script for ESLint. PR [#650](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/650) by [@alejsdev](https://github.com/alejsdev). * ⚙️ Add Prettier config. PR [#647](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/647) by [@alejsdev](https://github.com/alejsdev). * 🔧 Update pre-commit config. PR [#645](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/645) by [@alejsdev](https://github.com/alejsdev). From adfc775fa05068f003bfc9f55d413841b94b8b86 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:45:49 +0100 Subject: [PATCH 260/771] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unused=20schema?= =?UTF-8?q?s=20(#656)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/schemas/__init__.py | 4 ---- backend/app/schemas/item.py | 35 --------------------------------- backend/app/schemas/msg.py | 5 ----- backend/app/schemas/token.py | 10 ---------- backend/app/schemas/user.py | 35 --------------------------------- 5 files changed, 89 deletions(-) delete mode 100644 backend/app/schemas/__init__.py delete mode 100644 backend/app/schemas/item.py delete mode 100644 backend/app/schemas/msg.py delete mode 100644 backend/app/schemas/token.py delete mode 100644 backend/app/schemas/user.py diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py deleted file mode 100644 index a6ce47fc01..0000000000 --- a/backend/app/schemas/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .item import Item, ItemCreate, ItemInDB, ItemUpdate -from .msg import Msg -from .token import Token, TokenPayload -from .user import User, UserCreate, UserInDB, UserUpdate \ No newline at end of file diff --git a/backend/app/schemas/item.py b/backend/app/schemas/item.py deleted file mode 100644 index 2a541125cd..0000000000 --- a/backend/app/schemas/item.py +++ /dev/null @@ -1,35 +0,0 @@ -from pydantic import BaseModel, ConfigDict - - -# Shared properties -class ItemBase(BaseModel): - title: str | None = None - description: str | None = None - - -# Properties to receive on item creation -class ItemCreate(ItemBase): - title: str - - -# Properties to receive on item update -class ItemUpdate(ItemBase): - pass - - -# Properties shared by models stored in DB -class ItemInDBBase(ItemBase): - id: int - title: str - owner_id: int - model_config = ConfigDict(from_attributes=True) - - -# Properties to return to client -class Item(ItemInDBBase): - pass - - -# Properties properties stored in DB -class ItemInDB(ItemInDBBase): - pass diff --git a/backend/app/schemas/msg.py b/backend/app/schemas/msg.py deleted file mode 100644 index 945e0c627b..0000000000 --- a/backend/app/schemas/msg.py +++ /dev/null @@ -1,5 +0,0 @@ -from pydantic import BaseModel - - -class Msg(BaseModel): - msg: str diff --git a/backend/app/schemas/token.py b/backend/app/schemas/token.py deleted file mode 100644 index 17c74027e5..0000000000 --- a/backend/app/schemas/token.py +++ /dev/null @@ -1,10 +0,0 @@ -from pydantic import BaseModel - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenPayload(BaseModel): - sub: int | None = None diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py deleted file mode 100644 index 916567f7ed..0000000000 --- a/backend/app/schemas/user.py +++ /dev/null @@ -1,35 +0,0 @@ -from pydantic import BaseModel, ConfigDict, EmailStr - - -# Shared properties -class UserBase(BaseModel): - email: EmailStr | None = None - is_active: bool | None = True - is_superuser: bool = False - full_name: str | None = None - - -# Properties to receive via API on creation -class UserCreate(UserBase): - email: EmailStr - password: str - - -# Properties to receive via API on update -class UserUpdate(UserBase): - password: str | None = None - - -class UserInDBBase(UserBase): - id: int | None = None - model_config = ConfigDict(from_attributes=True) - - -# Additional properties to return via API -class User(UserInDBBase): - pass - - -# Additional properties stored in DB -class UserInDB(UserInDBBase): - hashed_password: str From 3f98892b084cc4104990d235660a02f7bd7dd458 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 19:46:11 +0000 Subject: [PATCH 261/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index faedcb18f3..a5e67c840a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -61,6 +61,7 @@ ### Refactors +* 🔥 Remove unused schemas. PR [#656](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/656) by [@alejsdev](https://github.com/alejsdev). * 🔥 Remove old frontend. PR [#649](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/649) by [@tiangolo](https://github.com/tiangolo). * ♻ Move project source files to top level from src, update Sentry dependency. PR [#630](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/630) by [@estebanx64](https://github.com/estebanx64). * ♻ Refactor Python folder tree. PR [#629](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/629) by [@estebanx64](https://github.com/estebanx64). From 111d8e8ab982e414dc2767f7a0d1fa1217aa6f7f Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:47:58 +0100 Subject: [PATCH 262/771] =?UTF-8?q?=F0=9F=93=B8=20Add=20new=20screenshots?= =?UTF-8?q?=20(#657)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++++++++- img/dashboard-create.png | Bin 0 -> 79627 bytes img/dashboard-items.png | Bin 0 -> 65084 bytes img/dashboard-user-settings.png | Bin 0 -> 62823 bytes img/dashboard.png | Bin 74826 -> 70654 bytes img/login.png | Bin 32648 -> 36530 bytes 6 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 img/dashboard-create.png create mode 100644 img/dashboard-items.png create mode 100644 img/dashboard-user-settings.png diff --git a/README.md b/README.md index 7b8eeb9b35..30554ef564 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,22 @@ Some of the future new features and changes: [![API docs](img/login.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) -### Dashboard - Create User +### Dashboard - Admin [![API docs](img/dashboard.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) +### Dashboard - Create User + +[![API docs](img/dashboard-create.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) + +### Dashboard - Items + +[![API docs](img/dashboard-items.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) + +### Dashboard - User Settings + +[![API docs](img/dashboard-user-settings.png)](https://github.com/tiangolo/full-stack-fastapi-postgresql) + ## Technology Stack and Features - ⚡ [**FastAPI**](https://fastapi.tiangolo.com) for the Python backend API. diff --git a/img/dashboard-create.png b/img/dashboard-create.png new file mode 100644 index 0000000000000000000000000000000000000000..a394141f7bac86ee4fac8e55a4e8ca8e2d480b72 GIT binary patch literal 79627 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ⅇ27Vk@4wfiIf0@ZRhe`yQH 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

pKDOcJ5e9)?1uJlYhoU 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 I;?lSO2H^O-)c^nh literal 0 HcmV?d00001 diff --git a/img/dashboard-items.png b/img/dashboard-items.png new file mode 100644 index 0000000000000000000000000000000000000000..f50e2e834ed05c45faaf2697328785118bfad92a GIT binary patch literal 65084 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ÙMi)0rzk&>~)*<+0%?Wbm9$tlOC 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 C+D!%k literal 0 HcmV?d00001 diff --git a/img/dashboard-user-settings.png b/img/dashboard-user-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..8da2e21df70422019e0f75cdd41f6623323ad4b5 GIT binary patch literal 62823 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�};kdJLo;Qr3nv$O0FHyb@l^}R`_nCJ7! z*y)^|jTwVfbC~R3OvApVjXBSzT~4T`95FIcg zemD5ii-v2pAFPcG_b*7HtPGo;W3iPZ=;s*4U~R#d1Y$G*1jpDJv+;oL?&68$rP8|A ztzZ!UF=|j50nfzvt|l+X26`1g7$7%K;Jxy|Q9AVm&IT)61ZVE*=Q&7UxhJQlRmOS1H-06mjVA((W14g+1dX*etwUB_WxXjm;}4R*4CaU- zv}k}x#Wt-NcU__x2|T1my9NlRW0<_=EXwbgZxjFR7>jt7J7y%XqQI~5b;Cc=Mk!|h zum!KAZnC6MG-{K#3Zh%?k-|#>G#^xtB>+@T^#}2JQwa7vUX@{cphM-cKA_5iqMX3T zG}ryos2w*D%eLJX`~>;R9=F?GXyC_qY=0^szSb6~>NcTfZMV$1B8M2CmJ`ZYHf;Gs zR~vd7cZg;*C%%%cD6DIdq!mw$_hv^N^b4=N$g*SNeOJh;Ik04|+N{5>_Ml+!PF7C! z)4h8s>8Pl{S~*#+?EP%{ZKFp=^y&YS_A0Km_l7bi2=|e*wdDkx8coaQA~KpSK>)h_ z&DVo{>aVBzT)+G-Lyj1n$?qPoGV=->KS!?wfRV3>PiPYb?JKZ&6Gnj-DE(dAP-W!E zr(i&l5&&9_U`r&_7GzpHG~lf62RxNs@$bI%<(vIyM0PHc3F70<;&dJ%rt zRf_u_o47d5Nd|C2l=R!FMpZyN>1oZ5_}4ObB?YmI4!P9ev|irk+3X-hw=d^TecUOq z`IOU=HCAN{xz$6M+4Kn*wzYz-j@v^SyUy%1bw|rHd&49_+mP~SeYi1kBDIP6>gWLM zrOAkq2lVIPPL*%b>oa0BL`xRFQ|7o1Y2C`)k>&ba0*5^k_HK5rDY!t!E;giUunK8l z4@=ZP1d9zEc(&h<6A3bDUXZw?Lv3}8ylbZ`~`dKT@NHFmUh=}^wz zRAjoRJKCR7f6sCJ?G+Gne71!*<_yVHFTV!`a1vAh0hL@5qw#pzv9OU*-lweCK3FpD z+UV?iGm-%E!u4=J`+yja-ae_Tt1FVFmZ9Hy$acK}gbPB^+e*B8g}sD60##K``F$Ay z5`1Gj-?ZVsXWtPstM*-7?#!PB$NDp|b! zX|CvVZTUs~dd{6ilTi&h#;I+|_ybA5huRv-gnvWXznZUW_qSdzt!3r?*8g_Z_sS)o zL%j2LKB5SzJ9s=!_JL3XTdL>QL>h)VJkS{#|Ix?ou0Pd z*zalr_BE1T-#?`5w)GPH$x2o3Nw4uYr_GMykE6!k6ensM=*6C_`u7IX@$}HwEF*qM jJ@`M4YWUB#Ow=Zqr9Nw)1bS;}`=E;f-!xwM_J@B1QKK@0 literal 0 HcmV?d00001 diff --git a/img/dashboard.png b/img/dashboard.png index ab3f86357ca982c3f10d22f4d5f88af13236c89f..0f034d691b05ef361ced9ea8261c89ee431390c0 100644 GIT binary patch literal 70654 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Ν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 diff --git a/img/login.png b/img/login.png index b731bd238f22c950df15b4fc9f628ec421707ca4..66e3a7202ff04def7dba8c9486f612c9603aa185 100644 GIT binary patch literal 36530 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 P4^ik#S{JjGZ$A7N&OIn0 From 220ca986d7d2eb4a69624a170b28cf55c0d766a9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Mar 2024 19:48:15 +0000 Subject: [PATCH 263/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index a5e67c840a..d1e6fc7db5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -99,6 +99,7 @@ ### Docs +* 📸 Add new screenshots . PR [#657](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/657) by [@alejsdev](https://github.com/alejsdev). * 📝 Refactor README into separate README.md files for backend, frontend, deployment, development. PR [#639](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/639) by [@tiangolo](https://github.com/tiangolo). * 📝 Update README. PR [#628](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/628) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action latest-changes and move release notes to independent file. PR [#619](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/619) by [@tiangolo](https://github.com/tiangolo). From 608a287df3c5f367c86e57886c1469900a6a578b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 9 Mar 2024 01:41:13 +0100 Subject: [PATCH 264/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20deployment=20fi?= =?UTF-8?q?les=20and=20docs=20(#660)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/core/config.py | 3 - deployment.md | 121 ++++++++++++++++++++++++++++--- docker-compose.override.yml | 25 +++++-- docker-compose.traefik.yml | 77 ++++++++++++++++++++ docker-compose.yml | 137 ++++++++++++++++-------------------- frontend/Dockerfile | 2 + scripts/deploy.sh | 1 - 7 files changed, 269 insertions(+), 97 deletions(-) create mode 100644 docker-compose.traefik.yml diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 5341e0ad4d..8aeb7eef2a 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -17,9 +17,6 @@ class Settings(BaseSettings): # 60 minutes * 24 hours * 8 days = 8 days ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 SERVER_HOST: AnyHttpUrl - # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins - # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \ - # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]' BACKEND_CORS_ORIGINS: list[AnyHttpUrl] | str = [] @field_validator("BACKEND_CORS_ORIGINS", mode="before") diff --git a/deployment.md b/deployment.md index f761b3687e..2bf3a6ce00 100644 --- a/deployment.md +++ b/deployment.md @@ -1,25 +1,124 @@ # FastAPI Project - Deployment -You can deploy the using Docker Compose with a main Traefik proxy outside handling communication to the outside world and HTTPS certificates. +You can deploy the project using Docker Compose in a remote server. -And you can use CI (continuous integration) systems to do it automatically. +It expects you to have a Traefik proxy handling communication to the outside world and HTTPS certificates. + +And you can use CI (continuous integration) systems to deploy automatically. But you have to configure a couple things first. -## Traefik network +## Preparation + +* Have a remote server ready and available. +* Configure the DNS records of your domain to point to the IP of the server you just created. +* Install and configure [Docker](https://docs.docker.com/engine/install/). +* Create a remote directory to store your code, for example: + +```bash +mkdir -p /root/code/fastapi-project/ +``` + +## Public Traefik + +We need a Traefik proxy to handle incoming connections and HTTPS certificates. + +### Traefik Docker Compose + +Copy the Traefik Docker Compose file to your server, to your code directory. You could do it with `rsync`: + +```bash +rsync -a docker-compose.traefik.yml root@your-server.example.com:/root/code/fastapi-project/ +``` + +### Traefik Public Network + +This Traefik will expect a Docker "public network" named `traefik-public` to communicate with your stack(s). + +This way, there will be a single public Traefik proxy that handles the communication (HTTP and HTTPS) with the outside world, and then behind that, you could have one or more stacks. -This stack expects the public Traefik network to be named `traefik-public`. +To create a Docker "public network" named `traefik-public` run: -If you need to use a different Traefik public network name, update it in the `docker-compose.yml` files, in the section: +```bash +docker network create traefik-public +``` + +### Traefik Environment Variables + +The Traefik Docker Compose file expects some environment variables to be set. -```YAML -networks: - traefik-public: - external: true +Create the environment variables for HTTP Basic Auth. + +* Create the username, e.g.: + +```bash +export USERNAME=admin +``` + +* Create an environment variable with the password, e.g.: + +```bash +export PASSWORD=changethis +``` + +* Use openssl to generate the "hashed" version of the password and store it in an environment variable: + +```bash +export HASHED_PASSWORD=$(openssl passwd -apr1 $PASSWORD) ``` -Change `traefik-public` to the name of the used Traefik network. And then update it in the file `.env`: +* Create an environment variable with the domain name, e.g.: ```bash -TRAEFIK_PUBLIC_NETWORK=traefik-public +export DOMAIN=fastapi-project.example.com ``` + +* Create an environment variable with the email for Let's Encrypt, e.g.: + +```bash +export EMAIL=admin@example.com +``` + +### Start the Traefik Docker Compose + +Now with the environment variables set and the `docker-compose.traefik.yml` in place, you can start the Traefik Docker Compose: + +```bash +docker compose -f docker-compose.traefik.yml up -d +``` + +## Deploy the FastAPI Project + +Now that you have Traefik in place you can deploy your FastAPI project with Docker Compose. + +You could configure the variables in the `.env` file to match your domain, or you could override them before running the `docker compose` command. + +For example: + +```bash +export DOMAIN=fastapi-project.example.com +``` + +And then deploy with Docker Compose: + +```bash +docker compose -f docker-compose.yml up -d +``` + +For production you wouldn't want to have the overrides in `docker-compose.override.yml`, so you would need to explicitly specify the file to use, `docker-compose.yml`. + +## URLs + +Replace `fastapi-project.example.com` with your domain: + +Frontend: https://fastapi-project.example.com + +Backend API docs: https://fastapi-project.example.com/docs + +Backend API base URL: https://fastapi-project.example.com/api/ + +PGAdmin: https://pgadmin.fastapi-project.example.com + +Flower: https://flower.fastapi-project.example.com + +Traefik UI: https://traefik.fastapi-project.example.com diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 5a6de73d22..248c597010 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -2,6 +2,9 @@ version: "3.3" services: proxy: + image: traefik:v2.3 + volumes: + - /var/run/docker.sock:/var/run/docker.sock ports: - "80:80" - "8090:8080" @@ -10,10 +13,13 @@ services: # Enable Docker in Traefik, so that it reads labels from Docker services - --providers.docker # Add a constraint to only use services with the label for this stack - # from the env var TRAEFIK_TAG - - --providers.docker.constraints=Label(`traefik.constraint-label-stack`, `${TRAEFIK_TAG?Variable not set}`) + - --providers.docker.constraints=Label(`traefik.constraint-label`, `traefik-public`) # Do not expose all Docker services, only the ones explicitly exposed - --providers.docker.exposedbydefault=false + # Create an entrypoint "http" listening on port 80 + - --entrypoints.http.address=:80 + # Create an entrypoint "https" listening on port 443 + - --entrypoints.https.address=:443 # Enable the access log, with HTTP requests - --accesslog # Enable the Traefik log, for configurations and errors @@ -23,8 +29,12 @@ services: # Enable the Dashboard and API in insecure mode for local development - --api.insecure=true labels: - - traefik.http.routers.${STACK_NAME?Variable not set}-traefik-public-http.rule=Host(`${DOMAIN?Variable not set}`) - - traefik.http.services.${STACK_NAME?Variable not set}-traefik-public.loadbalancer.server.port=80 + # Enable Traefik for this service, to make it available in the public network + - traefik.enable=true + - traefik.constraint-label=traefik-public + # Dummy https-redirect middleware that doesn't really redirect, only to + # allow running it locally + - traefik.http.middlewares.https-redirect.contenttype.autodetect=false db: ports: @@ -72,6 +82,13 @@ services: args: INSTALL_DEV: ${INSTALL_DEV-true} + frontend: + build: + context: ./frontend + args: + - VITE_API_URL=http://${DOMAIN?Variable not set} + - NODE_ENV=development + networks: traefik-public: # For local dev, don't expect an external Traefik network diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml new file mode 100644 index 0000000000..3c918968c2 --- /dev/null +++ b/docker-compose.traefik.yml @@ -0,0 +1,77 @@ +services: + traefik: + image: traefik:v2.3 + ports: + # Listen on port 80, default for HTTP, necessary to redirect to HTTPS + - 80:80 + # Listen on port 443, default for HTTPS + - 443:443 + restart: always + labels: + # Enable Traefik for this service, to make it available in the public network + - traefik.enable=true + # Use the traefik-public network (declared below) + - traefik.docker.network=traefik-public + # Define the port inside of the Docker service to use + - traefik.http.services.traefik-dashboard.loadbalancer.server.port=8080 + # Make Traefik use this domain (from an environment variable) in HTTP + - traefik.http.routers.traefik-dashboard-http.entrypoints=http + - traefik.http.routers.traefik-dashboard-http.rule=Host(`traefik.${DOMAIN?Variable not set}`) + # traefik-https the actual router using HTTPS + - traefik.http.routers.traefik-dashboard-https.entrypoints=https + - traefik.http.routers.traefik-dashboard-https.rule=Host(`traefik.${DOMAIN?Variable not set}`) + - traefik.http.routers.traefik-dashboard-https.tls=true + # Use the "le" (Let's Encrypt) resolver created below + - traefik.http.routers.traefik-dashboard-https.tls.certresolver=le + # Use the special Traefik service api@internal with the web UI/Dashboard + - traefik.http.routers.traefik-dashboard-https.service=api@internal + # https-redirect middleware to redirect HTTP to HTTPS + - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https + - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true + # traefik-http set up only to use the middleware to redirect to https + - traefik.http.routers.traefik-dashboard-http.middlewares=https-redirect + # admin-auth middleware with HTTP Basic auth + # Using the environment variables USERNAME and HASHED_PASSWORD + - traefik.http.middlewares.admin-auth.basicauth.users=${USERNAME?Variable not set}:${HASHED_PASSWORD?Variable not set} + # Enable HTTP Basic auth, using the middleware created above + - traefik.http.routers.traefik-dashboard-https.middlewares=admin-auth + volumes: + # Add Docker as a mounted volume, so that Traefik can read the labels of other services + - /var/run/docker.sock:/var/run/docker.sock:ro + # Mount the volume to store the certificates + - traefik-public-certificates:/certificates + command: + # Enable Docker in Traefik, so that it reads labels from Docker services + - --providers.docker + # Do not expose all Docker services, only the ones explicitly exposed + - --providers.docker.exposedbydefault=false + # Create an entrypoint "http" listening on port 80 + - --entrypoints.http.address=:80 + # Create an entrypoint "https" listening on port 443 + - --entrypoints.https.address=:443 + # Create the certificate resolver "le" for Let's Encrypt, uses the environment variable EMAIL + - --certificatesresolvers.le.acme.email=${EMAIL?Variable not set} + # Store the Let's Encrypt certificates in the mounted volume + - --certificatesresolvers.le.acme.storage=/certificates/acme.json + # Use the TLS Challenge for Let's Encrypt + - --certificatesresolvers.le.acme.tlschallenge=true + # Enable the access log, with HTTP requests + - --accesslog + # Enable the Traefik log, for configurations and errors + - --log + # Enable the Dashboard and API + - --api + networks: + # Use the public network created to be shared between Traefik and + # any other service that needs to be publicly available with HTTPS + - traefik-public + +volumes: + # Create a volume to store the certificates, even if the container is recreated + traefik-public-certificates: + +networks: + # Use the previously created public network "traefik-public", shared with other + # services that need to be publicly available via this Traefik + traefik-public: + external: true diff --git a/docker-compose.yml b/docker-compose.yml index 552fbda4e0..5f206e96eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,67 +1,5 @@ version: "3.3" services: - - proxy: - image: traefik:v2.3 - networks: - - ${TRAEFIK_PUBLIC_NETWORK?Variable not set} - - default - volumes: - - /var/run/docker.sock:/var/run/docker.sock - command: - # Enable Docker in Traefik, so that it reads labels from Docker services - - --providers.docker - # Add a constraint to only use services with the label for this stack - # from the env var TRAEFIK_TAG - - --providers.docker.constraints=Label(`traefik.constraint-label-stack`, `${TRAEFIK_TAG?Variable not set}`) - # Do not expose all Docker services, only the ones explicitly exposed - - --providers.docker.exposedbydefault=false - # Enable the access log, with HTTP requests - - --accesslog - # Enable the Traefik log, for configurations and errors - - --log - # Enable the Dashboard and API - - --api - labels: - # Enable Traefik for this service, to make it available in the public network - - traefik.enable=true - # Use the traefik-public network (declared below) - - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} - # Use the custom label "traefik.constraint-label=traefik-public" - # This public Traefik will only use services with this label - - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} - # traefik-http set up only to use the middleware to redirect to https - - traefik.http.middlewares.${STACK_NAME?Variable not set}-https-redirect.redirectscheme.scheme=https - - traefik.http.middlewares.${STACK_NAME?Variable not set}-https-redirect.redirectscheme.permanent=true - # Handle host with and without "www" to redirect to only one of them - # Uses environment variable DOMAIN - # To disable www redirection remove the Host() you want to discard, here and - # below for HTTPS - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.entrypoints=http - # traefik-https the actual router using HTTPS - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.entrypoints=https - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.tls=true - # Use the "le" (Let's Encrypt) resolver created below - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.tls.certresolver=le - # Define the port inside of the Docker service to use - - traefik.http.services.${STACK_NAME?Variable not set}-proxy.loadbalancer.server.port=80 - # Handle domain with and without "www" to redirect to only one - # To disable www redirection remove the next line - - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.regex=^https?://(www.)?(${DOMAIN?Variable not set})/(.*) - # Redirect a domain with www to non-www - # To disable it remove the next line - - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.replacement=https://${DOMAIN?Variable not set}/$${3} - # Redirect a domain without www to www - # To enable it remove the previous line and uncomment the next - # - traefik.http.middlewares.${STACK_NAME}-www-redirect.redirectregex.replacement=https://www.${DOMAIN}/$${3} - # Middleware to redirect www, to disable it remove the next line - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.middlewares=${STACK_NAME?Variable not set}-www-redirect - # Middleware to redirect www, and redirect HTTP to HTTPS - # to disable www redirection remove the section: ${STACK_NAME?Variable not set}-www-redirect, - - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.middlewares=${STACK_NAME?Variable not set}-www-redirect,${STACK_NAME?Variable not set}-https-redirect - db: image: postgres:12 volumes: @@ -74,7 +12,7 @@ services: pgadmin: image: dpage/pgadmin4 networks: - - ${TRAEFIK_PUBLIC_NETWORK?Variable not set} + - traefik-public - default depends_on: - db @@ -82,11 +20,11 @@ services: - .env labels: - traefik.enable=true - - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} - - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.rule=Host(`pgadmin.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.entrypoints=http - - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.middlewares=${STACK_NAME?Variable not set}-https-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.middlewares=https-redirect - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.rule=Host(`pgadmin.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.entrypoints=https - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.tls=true @@ -103,7 +41,7 @@ services: flower: image: mher/flower:0.9.7 networks: - - ${TRAEFIK_PUBLIC_NETWORK?Variable not set} + - traefik-public - default env_file: - .env @@ -114,11 +52,11 @@ services: # - "--broker_api=http://guest:guest@queue:15672/api//" labels: - traefik.enable=true - - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set} - - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set} + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.rule=Host(`flower.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.entrypoints=http - - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.middlewares=${STACK_NAME?Variable not set}-https-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.middlewares=https-redirect - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.rule=Host(`flower.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.entrypoints=https - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls=true @@ -127,6 +65,9 @@ services: backend: image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' + networks: + - traefik-public + - default depends_on: - db env_file: @@ -143,10 +84,21 @@ services: INSTALL_DEV: ${INSTALL_DEV-false} labels: - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`) + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public + - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) && PathPrefix(`/api`, `/docs`, `/redoc`) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http + + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) && PathPrefix(`/api`, `/docs`, `/redoc`) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le + + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect,${STACK_NAME?Variable not set}-www-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.middlewares=${STACK_NAME?Variable not set}-www-redirect celeryworker: image: '${DOCKER_IMAGE_CELERYWORKER?Variable not set}:${TAG-latest}' depends_on: @@ -166,18 +118,47 @@ services: frontend: image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' + networks: + - traefik-public + - default build: context: ./frontend - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 + args: + - VITE_API_URL=https://${DOMAIN?Variable not set} + - NODE_ENV=production + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public + + - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 + + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http + + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le + # Handle domain with and without "www" to redirect to only one + # To disable www redirection remove the next line + - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.regex=^http(s)?://www.(${DOMAIN?Variable not set})/(.*) + # Redirect a domain with www to non-www + # To disable it remove the next line + - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.replacement=http$${1}://${DOMAIN?Variable not set}/$${3} + # Redirect a domain without www to www + # To enable it remove the previous line and uncomment the next + # - traefik.http.middlewares.${STACK_NAME}-www-redirect.redirectregex.replacement=https://www.${DOMAIN}/$${3} + # Middleware to redirect www, to disable it remove the next line + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.middlewares=${STACK_NAME?Variable not set}-www-redirect + # Middleware to redirect www, and redirect HTTP to HTTPS + # to disable www redirection remove the section: ${STACK_NAME?Variable not set}-www-redirect, + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.middlewares=https-redirect,${STACK_NAME?Variable not set}-www-redirect volumes: app-db-data: networks: traefik-public: # Allow setting it to false for testing - external: ${TRAEFIK_PUBLIC_NETWORK_IS_EXTERNAL-true} + external: true diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 3fe5f9789a..126ed9f63d 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -9,6 +9,8 @@ RUN npm install COPY ./ /app/ +ARG VITE_API_URL=${VITE_API_URL} + RUN npm run build diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 55a86ee94c..99faa96bf7 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -4,7 +4,6 @@ set -e DOMAIN=${DOMAIN?Variable not set} \ -TRAEFIK_TAG=${TRAEFIK_TAG?Variable not set} \ STACK_NAME=${STACK_NAME?Variable not set} \ TAG=${TAG?Variable not set} \ docker-compose \ From 2810d50222f3e7efd146caff28d5920194a24b11 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 Mar 2024 00:41:36 +0000 Subject: [PATCH 265/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index d1e6fc7db5..436170c8e7 100644 --- a/release-notes.md +++ b/release-notes.md @@ -61,6 +61,7 @@ ### Refactors +* 📝 Update deployment files and docs. PR [#660](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/660) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove unused schemas. PR [#656](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/656) by [@alejsdev](https://github.com/alejsdev). * 🔥 Remove old frontend. PR [#649](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/649) by [@tiangolo](https://github.com/tiangolo). * ♻ Move project source files to top level from src, update Sentry dependency. PR [#630](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/630) by [@estebanx64](https://github.com/estebanx64). From e451811cbb2f0a986f1aa56f03f82ce44f72bbf2 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Sat, 9 Mar 2024 01:41:58 +0100 Subject: [PATCH 266/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20email?= =?UTF-8?q?=20templates=20(#659)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email-templates/build/new_account.html | 49 +++++++++---------- .../email-templates/build/reset_password.html | 49 +++++++++---------- .../app/email-templates/build/test_email.html | 48 +++++++++--------- .../app/email-templates/src/new_account.mjml | 20 ++++---- .../email-templates/src/reset_password.mjml | 26 +++++----- .../app/email-templates/src/test_email.mjml | 12 ++--- 6 files changed, 100 insertions(+), 104 deletions(-) diff --git a/backend/app/email-templates/build/new_account.html b/backend/app/email-templates/build/new_account.html index 9d82b563e8..344505033b 100644 --- a/backend/app/email-templates/build/new_account.html +++ b/backend/app/email-templates/build/new_account.html @@ -1,26 +1,25 @@

{{ project_name }} - New Account
You have a new account:
Username: {{ username }}
Password: {{ password }}
Go to Dashboard

+ .ReadMsgBody { width:100%; } + .ExternalClass { width:100%; } + .ExternalClass * { line-height:100%; } + body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; } + table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; } + img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; } + p { display:block;margin:13px 0; }
{{ project_name }} - New Account
Welcome to your new account!
Here are your account details:
Username: {{ username }}
Password: {{ password }}
Go to Dashboard

\ No newline at end of file diff --git a/backend/app/email-templates/build/reset_password.html b/backend/app/email-templates/build/reset_password.html index 888ce3bc52..e1d029bb82 100644 --- a/backend/app/email-templates/build/reset_password.html +++ b/backend/app/email-templates/build/reset_password.html @@ -1,26 +1,25 @@

{{ project_name }} - Password Recovery
We received a request to recover the password for user {{ username }} with email {{ email }}
Reset your password by clicking the button below:
Reset Password
Or open the following link:

The reset password link / button will expire in {{ valid_hours }} hours.
If you didn't request a password recovery you can disregard this email.
+ .ReadMsgBody { width:100%; } + .ExternalClass { width:100%; } + .ExternalClass * { line-height:100%; } + body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; } + table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; } + img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; } + p { display:block;margin:13px 0; }
{{ project_name }} - Password Recovery
Hello {{ username }}
We've received a request to reset your password. You can do it by clicking the button below:
Reset password
Or copy and paste the following link into your browser:
This password will expire in {{ valid_hours }} hours.

If you didn't request a password recovery you can disregard this email.
\ No newline at end of file diff --git a/backend/app/email-templates/build/test_email.html b/backend/app/email-templates/build/test_email.html index da78c57b11..04d0d85092 100644 --- a/backend/app/email-templates/build/test_email.html +++ b/backend/app/email-templates/build/test_email.html @@ -1,25 +1,25 @@

{{ project_name }}
Test email for: {{ email }}
+ .ReadMsgBody { width:100%; } + .ExternalClass { width:100%; } + .ExternalClass * { line-height:100%; } + body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; } + table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; } + img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; } + p { display:block;margin:13px 0; }
{{ project_name }}
Test email for: {{ email }}

\ No newline at end of file diff --git a/backend/app/email-templates/src/new_account.mjml b/backend/app/email-templates/src/new_account.mjml index 16c033b078..f41a3e3cf1 100644 --- a/backend/app/email-templates/src/new_account.mjml +++ b/backend/app/email-templates/src/new_account.mjml @@ -1,14 +1,14 @@ - - - - - {{ project_name }} - New Account - You have a new account: - Username: {{ username }} - Password: {{ password }} - Go to Dashboard - + + + + {{ project_name }} - New Account + Welcome to your new account! + Here are your account details: + Username: {{ username }} + Password: {{ password }} + Go to Dashboard + diff --git a/backend/app/email-templates/src/reset_password.mjml b/backend/app/email-templates/src/reset_password.mjml index 4f45ea285b..6cd04c176a 100644 --- a/backend/app/email-templates/src/reset_password.mjml +++ b/backend/app/email-templates/src/reset_password.mjml @@ -1,18 +1,16 @@ - - - - - {{ project_name }} - Password Recovery - We received a request to recover the password for user {{ username }} - with email {{ email }} - Reset your password by clicking the button below: - Reset Password - Or open the following link: -
{{ link }} - - The reset password link / button will expire in {{ valid_hours }} hours. - If you didn't request a password recovery you can disregard this email. + + + + {{ project_name }} - Password Recovery + Hello {{ username }} + We've received a request to reset your password. You can do it by clicking the button below: + Reset password + Or copy and paste the following link into your browser: + {{ link }} + This password will expire in {{ valid_hours }} hours. + + If you didn't request a password recovery you can disregard this email. diff --git a/backend/app/email-templates/src/test_email.mjml b/backend/app/email-templates/src/test_email.mjml index 5b9baa4594..45d58d6bac 100644 --- a/backend/app/email-templates/src/test_email.mjml +++ b/backend/app/email-templates/src/test_email.mjml @@ -1,10 +1,10 @@ - - - - - {{ project_name }} - Test email for: {{ email }} + + + + {{ project_name }} + Test email for: {{ email }} + From e1ceb13f1bd06a32d252575821b883d029a677a5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 Mar 2024 00:42:21 +0000 Subject: [PATCH 267/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 436170c8e7..03c00adadf 100644 --- a/release-notes.md +++ b/release-notes.md @@ -61,6 +61,7 @@ ### Refactors +* ♻️ Refactor email templates. PR [#659](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/659) by [@alejsdev](https://github.com/alejsdev). * 📝 Update deployment files and docs. PR [#660](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/660) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove unused schemas. PR [#656](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/656) by [@alejsdev](https://github.com/alejsdev). * 🔥 Remove old frontend. PR [#649](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/649) by [@tiangolo](https://github.com/tiangolo). From 7bf1fc01db056ca6cea62ac74279f667f7a92a3a Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Sat, 9 Mar 2024 21:13:31 +0100 Subject: [PATCH 268/771] =?UTF-8?q?=F0=9F=92=AC=20Improve=20Delete=20Accou?= =?UTF-8?q?nt=20description=20and=20confirmation=20(#661)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- frontend/src/components/UserSettings/DeleteAccount.tsx | 4 ++-- frontend/src/components/UserSettings/DeleteConfirmation.tsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/UserSettings/DeleteAccount.tsx b/frontend/src/components/UserSettings/DeleteAccount.tsx index a9c9a0f1f4..09c640e383 100644 --- a/frontend/src/components/UserSettings/DeleteAccount.tsx +++ b/frontend/src/components/UserSettings/DeleteAccount.tsx @@ -19,8 +19,8 @@ const DeleteAccount: React.FC = () => { Delete Account - Are you sure you want to delete your account? This action cannot be - undone. + Permanently delete your data and everything associated with your + account. + Page {page} + + + ) } From a34d6330a26e2c0edab2ab13d0a6eaed12b7c0e7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 27 Jun 2024 19:50:16 +0000 Subject: [PATCH 540/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 0ef0fef2ba..c308ac7656 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Introduce pagination in items. PR [#1239](https://github.com/tiangolo/full-stack-fastapi-template/pull/1239) by [@patrick91](https://github.com/patrick91). * 🗃️ Add max_length validation for database models and input data. PR [#1233](https://github.com/tiangolo/full-stack-fastapi-template/pull/1233) by [@estebanx64](https://github.com/estebanx64). * ✨ Add TanStack React Query devtools in dev build. PR [#1217](https://github.com/tiangolo/full-stack-fastapi-template/pull/1217) by [@tomerb](https://github.com/tomerb). * ✨ Add support for deploying multiple environments (staging, production) to the same server. PR [#1128](https://github.com/tiangolo/full-stack-fastapi-template/pull/1128) by [@tiangolo](https://github.com/tiangolo). From d24155d42f5ed8863d8585d3ca894bd48c60e26b Mon Sep 17 00:00:00 2001 From: Tomer Barletz Date: Thu, 27 Jun 2024 22:53:51 +0300 Subject: [PATCH 541/771] =?UTF-8?q?=F0=9F=8E=A8=20Run=20biome=20after=20Op?= =?UTF-8?q?enAPI=20client=20generation=20(#1226)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/biome.json | 2 +- frontend/package.json | 2 +- frontend/src/client/core/ApiError.ts | 38 +- frontend/src/client/core/ApiRequestOptions.ts | 31 +- frontend/src/client/core/ApiResult.ts | 12 +- frontend/src/client/core/CancelablePromise.ts | 236 ++--- frontend/src/client/core/OpenAPI.ts | 77 +- frontend/src/client/core/request.ts | 638 +++++------ frontend/src/client/core/types.ts | 18 +- frontend/src/client/index.ts | 15 +- frontend/src/client/models.ts | 165 ++- frontend/src/client/schemas.ts | 696 ++++++------ frontend/src/client/services.ts | 988 +++++++++--------- 13 files changed, 1482 insertions(+), 1436 deletions(-) diff --git a/frontend/biome.json b/frontend/biome.json index b6e397eb49..1c2adb8db0 100644 --- a/frontend/biome.json +++ b/frontend/biome.json @@ -4,7 +4,7 @@ "enabled": true }, "files": { - "ignore": ["node_modules", "src/client/", "src/routeTree.gen.ts"] + "ignore": ["node_modules", "src/routeTree.gen.ts"] }, "linter": { "enabled": true, diff --git a/frontend/package.json b/frontend/package.json index 55d6e79b2c..0aff516f4c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,7 @@ "build": "tsc && vite build", "lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./", "preview": "vite preview", - "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true" + "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true && biome format --write ./src/client" }, "dependencies": { "@chakra-ui/icons": "2.1.1", diff --git a/frontend/src/client/core/ApiError.ts b/frontend/src/client/core/ApiError.ts index 36675d288a..5499aa8f05 100644 --- a/frontend/src/client/core/ApiError.ts +++ b/frontend/src/client/core/ApiError.ts @@ -1,21 +1,25 @@ -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; +import type { ApiRequestOptions } from "./ApiRequestOptions" +import type { ApiResult } from "./ApiResult" export class ApiError extends Error { - public readonly url: string; - public readonly status: number; - public readonly statusText: string; - public readonly body: unknown; - public readonly request: ApiRequestOptions; + public readonly url: string + public readonly status: number + public readonly statusText: string + public readonly body: unknown + public readonly request: ApiRequestOptions - constructor(request: ApiRequestOptions, response: ApiResult, message: string) { - super(message); + constructor( + request: ApiRequestOptions, + response: ApiResult, + message: string, + ) { + super(message) - this.name = 'ApiError'; - this.url = response.url; - this.status = response.status; - this.statusText = response.statusText; - this.body = response.body; - this.request = request; - } -} \ No newline at end of file + this.name = "ApiError" + this.url = response.url + this.status = response.status + this.statusText = response.statusText + this.body = response.body + this.request = request + } +} diff --git a/frontend/src/client/core/ApiRequestOptions.ts b/frontend/src/client/core/ApiRequestOptions.ts index 8f8d4d159e..4cc259284f 100644 --- a/frontend/src/client/core/ApiRequestOptions.ts +++ b/frontend/src/client/core/ApiRequestOptions.ts @@ -1,13 +1,20 @@ export type ApiRequestOptions = { - readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; - readonly url: string; - readonly path?: Record; - readonly cookies?: Record; - readonly headers?: Record; - readonly query?: Record; - readonly formData?: Record; - readonly body?: any; - readonly mediaType?: string; - readonly responseHeader?: string; - readonly errors?: Record; -}; \ No newline at end of file + readonly method: + | "GET" + | "PUT" + | "POST" + | "DELETE" + | "OPTIONS" + | "HEAD" + | "PATCH" + readonly url: string + readonly path?: Record + readonly cookies?: Record + readonly headers?: Record + readonly query?: Record + readonly formData?: Record + readonly body?: any + readonly mediaType?: string + readonly responseHeader?: string + readonly errors?: Record +} diff --git a/frontend/src/client/core/ApiResult.ts b/frontend/src/client/core/ApiResult.ts index 4c58e39138..f88b8c64f1 100644 --- a/frontend/src/client/core/ApiResult.ts +++ b/frontend/src/client/core/ApiResult.ts @@ -1,7 +1,7 @@ export type ApiResult = { - readonly body: TData; - readonly ok: boolean; - readonly status: number; - readonly statusText: string; - readonly url: string; -}; \ No newline at end of file + readonly body: TData + readonly ok: boolean + readonly status: number + readonly statusText: string + readonly url: string +} diff --git a/frontend/src/client/core/CancelablePromise.ts b/frontend/src/client/core/CancelablePromise.ts index ccc082e8f2..f47db79eae 100644 --- a/frontend/src/client/core/CancelablePromise.ts +++ b/frontend/src/client/core/CancelablePromise.ts @@ -1,126 +1,126 @@ export class CancelError extends Error { - constructor(message: string) { - super(message); - this.name = 'CancelError'; - } - - public get isCancelled(): boolean { - return true; - } + constructor(message: string) { + super(message) + this.name = "CancelError" + } + + public get isCancelled(): boolean { + return true + } } export interface OnCancel { - readonly isResolved: boolean; - readonly isRejected: boolean; - readonly isCancelled: boolean; + readonly isResolved: boolean + readonly isRejected: boolean + readonly isCancelled: boolean - (cancelHandler: () => void): void; + (cancelHandler: () => void): void } export class CancelablePromise implements Promise { - private _isResolved: boolean; - private _isRejected: boolean; - private _isCancelled: boolean; - readonly cancelHandlers: (() => void)[]; - readonly promise: Promise; - private _resolve?: (value: T | PromiseLike) => void; - private _reject?: (reason?: unknown) => void; - - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: unknown) => void, - onCancel: OnCancel - ) => void - ) { - this._isResolved = false; - this._isRejected = false; - this._isCancelled = false; - this.cancelHandlers = []; - this.promise = new Promise((resolve, reject) => { - this._resolve = resolve; - this._reject = reject; - - const onResolve = (value: T | PromiseLike): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isResolved = true; - if (this._resolve) this._resolve(value); - }; - - const onReject = (reason?: unknown): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isRejected = true; - if (this._reject) this._reject(reason); - }; - - const onCancel = (cancelHandler: () => void): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this.cancelHandlers.push(cancelHandler); - }; - - Object.defineProperty(onCancel, 'isResolved', { - get: (): boolean => this._isResolved, - }); - - Object.defineProperty(onCancel, 'isRejected', { - get: (): boolean => this._isRejected, - }); - - Object.defineProperty(onCancel, 'isCancelled', { - get: (): boolean => this._isCancelled, - }); - - return executor(onResolve, onReject, onCancel as OnCancel); - }); - } - - get [Symbol.toStringTag]() { - return "Cancellable Promise"; - } - - public then( - onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null - ): Promise { - return this.promise.then(onFulfilled, onRejected); - } - - public catch( - onRejected?: ((reason: unknown) => TResult | PromiseLike) | null - ): Promise { - return this.promise.catch(onRejected); - } - - public finally(onFinally?: (() => void) | null): Promise { - return this.promise.finally(onFinally); - } - - public cancel(): void { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isCancelled = true; - if (this.cancelHandlers.length) { - try { - for (const cancelHandler of this.cancelHandlers) { - cancelHandler(); - } - } catch (error) { - console.warn('Cancellation threw an error', error); - return; - } - } - this.cancelHandlers.length = 0; - if (this._reject) this._reject(new CancelError('Request aborted')); - } - - public get isCancelled(): boolean { - return this._isCancelled; - } -} \ No newline at end of file + private _isResolved: boolean + private _isRejected: boolean + private _isCancelled: boolean + readonly cancelHandlers: (() => void)[] + readonly promise: Promise + private _resolve?: (value: T | PromiseLike) => void + private _reject?: (reason?: unknown) => void + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: unknown) => void, + onCancel: OnCancel, + ) => void, + ) { + this._isResolved = false + this._isRejected = false + this._isCancelled = false + this.cancelHandlers = [] + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve + this._reject = reject + + const onResolve = (value: T | PromiseLike): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return + } + this._isResolved = true + if (this._resolve) this._resolve(value) + } + + const onReject = (reason?: unknown): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return + } + this._isRejected = true + if (this._reject) this._reject(reason) + } + + const onCancel = (cancelHandler: () => void): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return + } + this.cancelHandlers.push(cancelHandler) + } + + Object.defineProperty(onCancel, "isResolved", { + get: (): boolean => this._isResolved, + }) + + Object.defineProperty(onCancel, "isRejected", { + get: (): boolean => this._isRejected, + }) + + Object.defineProperty(onCancel, "isCancelled", { + get: (): boolean => this._isCancelled, + }) + + return executor(onResolve, onReject, onCancel as OnCancel) + }) + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise" + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, + ): Promise { + return this.promise.then(onFulfilled, onRejected) + } + + public catch( + onRejected?: ((reason: unknown) => TResult | PromiseLike) | null, + ): Promise { + return this.promise.catch(onRejected) + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.promise.finally(onFinally) + } + + public cancel(): void { + if (this._isResolved || this._isRejected || this._isCancelled) { + return + } + this._isCancelled = true + if (this.cancelHandlers.length) { + try { + for (const cancelHandler of this.cancelHandlers) { + cancelHandler() + } + } catch (error) { + console.warn("Cancellation threw an error", error) + return + } + } + this.cancelHandlers.length = 0 + if (this._reject) this._reject(new CancelError("Request aborted")) + } + + public get isCancelled(): boolean { + return this._isCancelled + } +} diff --git a/frontend/src/client/core/OpenAPI.ts b/frontend/src/client/core/OpenAPI.ts index c1164c7b36..746df5e61d 100644 --- a/frontend/src/client/core/OpenAPI.ts +++ b/frontend/src/client/core/OpenAPI.ts @@ -1,58 +1,57 @@ -import type { AxiosRequestConfig, AxiosResponse } from 'axios';import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { TResult } from './types'; +import type { AxiosRequestConfig, AxiosResponse } from "axios" +import type { ApiRequestOptions } from "./ApiRequestOptions" +import type { TResult } from "./types" -type Headers = Record; -type Middleware = (value: T) => T | Promise; -type Resolver = (options: ApiRequestOptions) => Promise; +type Headers = Record +type Middleware = (value: T) => T | Promise +type Resolver = (options: ApiRequestOptions) => Promise export class Interceptors { - _fns: Middleware[]; + _fns: Middleware[] constructor() { - this._fns = []; + this._fns = [] } eject(fn: Middleware) { - const index = this._fns.indexOf(fn); + const index = this._fns.indexOf(fn) if (index !== -1) { - this._fns = [ - ...this._fns.slice(0, index), - ...this._fns.slice(index + 1), - ]; + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)] } } use(fn: Middleware) { - this._fns = [...this._fns, fn]; + this._fns = [...this._fns, fn] } } export type OpenAPIConfig = { - BASE: string; - CREDENTIALS: 'include' | 'omit' | 'same-origin'; - ENCODE_PATH?: ((path: string) => string) | undefined; - HEADERS?: Headers | Resolver | undefined; - PASSWORD?: string | Resolver | undefined; - RESULT?: TResult; - TOKEN?: string | Resolver | undefined; - USERNAME?: string | Resolver | undefined; - VERSION: string; - WITH_CREDENTIALS: boolean; - interceptors: {request: Interceptors; - response: Interceptors;}; -}; + BASE: string + CREDENTIALS: "include" | "omit" | "same-origin" + ENCODE_PATH?: ((path: string) => string) | undefined + HEADERS?: Headers | Resolver | undefined + PASSWORD?: string | Resolver | undefined + RESULT?: TResult + TOKEN?: string | Resolver | undefined + USERNAME?: string | Resolver | undefined + VERSION: string + WITH_CREDENTIALS: boolean + interceptors: { + request: Interceptors + response: Interceptors + } +} export const OpenAPI: OpenAPIConfig = { - BASE: '', - CREDENTIALS: 'include', - ENCODE_PATH: undefined, - HEADERS: undefined, - PASSWORD: undefined, - RESULT: 'body', - TOKEN: undefined, - USERNAME: undefined, - VERSION: '0.1.0', - WITH_CREDENTIALS: false, - interceptors: {request: new Interceptors(),response: new Interceptors(), - }, -}; \ No newline at end of file + BASE: "", + CREDENTIALS: "include", + ENCODE_PATH: undefined, + HEADERS: undefined, + PASSWORD: undefined, + RESULT: "body", + TOKEN: undefined, + USERNAME: undefined, + VERSION: "0.1.0", + WITH_CREDENTIALS: false, + interceptors: { request: new Interceptors(), response: new Interceptors() }, +} diff --git a/frontend/src/client/core/request.ts b/frontend/src/client/core/request.ts index 080a6f428f..99d38b46f1 100644 --- a/frontend/src/client/core/request.ts +++ b/frontend/src/client/core/request.ts @@ -1,295 +1,319 @@ -import axios from 'axios'; -import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'; - -import { ApiError } from './ApiError'; -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; -import { CancelablePromise } from './CancelablePromise'; -import type { OnCancel } from './CancelablePromise'; -import type { OpenAPIConfig } from './OpenAPI'; +import axios from "axios" +import type { + AxiosError, + AxiosRequestConfig, + AxiosResponse, + AxiosInstance, +} from "axios" + +import { ApiError } from "./ApiError" +import type { ApiRequestOptions } from "./ApiRequestOptions" +import type { ApiResult } from "./ApiResult" +import { CancelablePromise } from "./CancelablePromise" +import type { OnCancel } from "./CancelablePromise" +import type { OpenAPIConfig } from "./OpenAPI" export const isString = (value: unknown): value is string => { - return typeof value === 'string'; -}; + return typeof value === "string" +} export const isStringWithValue = (value: unknown): value is string => { - return isString(value) && value !== ''; -}; + return isString(value) && value !== "" +} export const isBlob = (value: any): value is Blob => { - return value instanceof Blob; -}; + return value instanceof Blob +} export const isFormData = (value: unknown): value is FormData => { - return value instanceof FormData; -}; + return value instanceof FormData +} export const isSuccess = (status: number): boolean => { - return status >= 200 && status < 300; -}; + return status >= 200 && status < 300 +} export const base64 = (str: string): string => { - try { - return btoa(str); - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString('base64'); - } -}; + try { + return btoa(str) + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString("base64") + } +} export const getQueryString = (params: Record): string => { - const qs: string[] = []; + const qs: string[] = [] - const append = (key: string, value: unknown) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); - }; + const append = (key: string, value: unknown) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) + } - const encodePair = (key: string, value: unknown) => { - if (value === undefined || value === null) { - return; - } + const encodePair = (key: string, value: unknown) => { + if (value === undefined || value === null) { + return + } - if (Array.isArray(value)) { - value.forEach(v => encodePair(key, v)); - } else if (typeof value === 'object') { - Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); - } else { - append(key, value); - } - }; + if (Array.isArray(value)) { + value.forEach((v) => encodePair(key, v)) + } else if (typeof value === "object") { + Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)) + } else { + append(key, value) + } + } - Object.entries(params).forEach(([key, value]) => encodePair(key, value)); + Object.entries(params).forEach(([key, value]) => encodePair(key, value)) - return qs.length ? `?${qs.join('&')}` : ''; -}; + return qs.length ? `?${qs.join("&")}` : "" +} const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI; - - const path = options.url - .replace('{api-version}', config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])); - } - return substring; - }); - - const url = config.BASE + path; - return options.query ? url + getQueryString(options.query) : url; -}; - -export const getFormData = (options: ApiRequestOptions): FormData | undefined => { - if (options.formData) { - const formData = new FormData(); - - const process = (key: string, value: unknown) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value); - } else { - formData.append(key, JSON.stringify(value)); - } - }; - - Object.entries(options.formData) - .filter(([, value]) => value !== undefined && value !== null) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach(v => process(key, v)); - } else { - process(key, value); - } - }); - - return formData; - } - return undefined; -}; - -type Resolver = (options: ApiRequestOptions) => Promise; - -export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { - if (typeof resolver === 'function') { - return (resolver as Resolver)(options); - } - return resolver; -}; - -export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise> => { - const [token, username, password, additionalHeaders] = await Promise.all([ - resolve(options, config.TOKEN), - resolve(options, config.USERNAME), - resolve(options, config.PASSWORD), - resolve(options, config.HEADERS), - ]); - - const headers = Object.entries({ - Accept: 'application/json', - ...additionalHeaders, - ...options.headers, - }) - .filter(([, value]) => value !== undefined && value !== null) - .reduce((headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), {} as Record); - - if (isStringWithValue(token)) { - headers['Authorization'] = `Bearer ${token}`; - } - - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`); - headers['Authorization'] = `Basic ${credentials}`; - } - - if (options.body !== undefined) { - if (options.mediaType) { - headers['Content-Type'] = options.mediaType; - } else if (isBlob(options.body)) { - headers['Content-Type'] = options.body.type || 'application/octet-stream'; - } else if (isString(options.body)) { - headers['Content-Type'] = 'text/plain'; - } else if (!isFormData(options.body)) { - headers['Content-Type'] = 'application/json'; - } - } else if (options.formData !== undefined) { - if (options.mediaType) { - headers['Content-Type'] = options.mediaType; - } - } - - return headers; -}; + const encoder = config.ENCODE_PATH || encodeURI + + const path = options.url + .replace("{api-version}", config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])) + } + return substring + }) + + const url = config.BASE + path + return options.query ? url + getQueryString(options.query) : url +} + +export const getFormData = ( + options: ApiRequestOptions, +): FormData | undefined => { + if (options.formData) { + const formData = new FormData() + + const process = (key: string, value: unknown) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value) + } else { + formData.append(key, JSON.stringify(value)) + } + } + + Object.entries(options.formData) + .filter(([, value]) => value !== undefined && value !== null) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((v) => process(key, v)) + } else { + process(key, value) + } + }) + + return formData + } + return undefined +} + +type Resolver = (options: ApiRequestOptions) => Promise + +export const resolve = async ( + options: ApiRequestOptions, + resolver?: T | Resolver, +): Promise => { + if (typeof resolver === "function") { + return (resolver as Resolver)(options) + } + return resolver +} + +export const getHeaders = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, +): Promise> => { + const [token, username, password, additionalHeaders] = await Promise.all([ + resolve(options, config.TOKEN), + resolve(options, config.USERNAME), + resolve(options, config.PASSWORD), + resolve(options, config.HEADERS), + ]) + + const headers = Object.entries({ + Accept: "application/json", + ...additionalHeaders, + ...options.headers, + }) + .filter(([, value]) => value !== undefined && value !== null) + .reduce( + (headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), + {} as Record, + ) + + if (isStringWithValue(token)) { + headers["Authorization"] = `Bearer ${token}` + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`) + headers["Authorization"] = `Basic ${credentials}` + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers["Content-Type"] = options.mediaType + } else if (isBlob(options.body)) { + headers["Content-Type"] = options.body.type || "application/octet-stream" + } else if (isString(options.body)) { + headers["Content-Type"] = "text/plain" + } else if (!isFormData(options.body)) { + headers["Content-Type"] = "application/json" + } + } else if (options.formData !== undefined) { + if (options.mediaType) { + headers["Content-Type"] = options.mediaType + } + } + + return headers +} export const getRequestBody = (options: ApiRequestOptions): unknown => { - if (options.body) { - return options.body; - } - return undefined; -}; + if (options.body) { + return options.body + } + return undefined +} export const sendRequest = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: unknown, - formData: FormData | undefined, - headers: Record, - onCancel: OnCancel, - axiosClient: AxiosInstance + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: unknown, + formData: FormData | undefined, + headers: Record, + onCancel: OnCancel, + axiosClient: AxiosInstance, ): Promise> => { - const controller = new AbortController(); - - let requestConfig: AxiosRequestConfig = { - data: body ?? formData, - headers, - method: options.method, - signal: controller.signal, - url, - withCredentials: config.WITH_CREDENTIALS, - }; - - onCancel(() => controller.abort()); - - for (const fn of config.interceptors.request._fns) { - requestConfig = await fn(requestConfig) - } - - try { - return await axiosClient.request(requestConfig); - } catch (error) { - const axiosError = error as AxiosError; - if (axiosError.response) { - return axiosError.response; - } - throw error; - } -}; - -export const getResponseHeader = (response: AxiosResponse, responseHeader?: string): string | undefined => { - if (responseHeader) { - const content = response.headers[responseHeader]; - if (isString(content)) { - return content; - } - } - return undefined; -}; + const controller = new AbortController() + + let requestConfig: AxiosRequestConfig = { + data: body ?? formData, + headers, + method: options.method, + signal: controller.signal, + url, + withCredentials: config.WITH_CREDENTIALS, + } + + onCancel(() => controller.abort()) + + for (const fn of config.interceptors.request._fns) { + requestConfig = await fn(requestConfig) + } + + try { + return await axiosClient.request(requestConfig) + } catch (error) { + const axiosError = error as AxiosError + if (axiosError.response) { + return axiosError.response + } + throw error + } +} + +export const getResponseHeader = ( + response: AxiosResponse, + responseHeader?: string, +): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader] + if (isString(content)) { + return content + } + } + return undefined +} export const getResponseBody = (response: AxiosResponse): unknown => { - if (response.status !== 204) { - return response.data; - } - return undefined; -}; - -export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { - const errors: Record = { - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Failed', - 413: 'Payload Too Large', - 414: 'URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Range Not Satisfiable', - 417: 'Expectation Failed', - 418: 'Im a teapot', - 421: 'Misdirected Request', - 422: 'Unprocessable Content', - 423: 'Locked', - 424: 'Failed Dependency', - 425: 'Too Early', - 426: 'Upgrade Required', - 428: 'Precondition Required', - 429: 'Too Many Requests', - 431: 'Request Header Fields Too Large', - 451: 'Unavailable For Legal Reasons', - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported', - 506: 'Variant Also Negotiates', - 507: 'Insufficient Storage', - 508: 'Loop Detected', - 510: 'Not Extended', - 511: 'Network Authentication Required', - ...options.errors, - } - - const error = errors[result.status]; - if (error) { - throw new ApiError(options, result, error); - } - - if (!result.ok) { - const errorStatus = result.status ?? 'unknown'; - const errorStatusText = result.statusText ?? 'unknown'; - const errorBody = (() => { - try { - return JSON.stringify(result.body, null, 2); - } catch (e) { - return undefined; - } - })(); - - throw new ApiError(options, result, - `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` - ); - } -}; + if (response.status !== 204) { + return response.data + } + return undefined +} + +export const catchErrorCodes = ( + options: ApiRequestOptions, + result: ApiResult, +): void => { + const errors: Record = { + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Payload Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 417: "Expectation Failed", + 418: "Im a teapot", + 421: "Misdirected Request", + 422: "Unprocessable Content", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required", + ...options.errors, + } + + const error = errors[result.status] + if (error) { + throw new ApiError(options, result, error) + } + + if (!result.ok) { + const errorStatus = result.status ?? "unknown" + const errorStatusText = result.statusText ?? "unknown" + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2) + } catch (e) { + return undefined + } + })() + + throw new ApiError( + options, + result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`, + ) + } +} /** * Request method @@ -299,38 +323,54 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): * @returns CancelablePromise * @throws ApiError */ -export const request = (config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options); - const formData = getFormData(options); - const body = getRequestBody(options); - const headers = await getHeaders(config, options); - - if (!onCancel.isCancelled) { - let response = await sendRequest(config, options, url, body, formData, headers, onCancel, axiosClient); - - for (const fn of config.interceptors.response._fns) { - response = await fn(response) - } - - const responseBody = getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); - - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader ?? responseBody, - }; - - catchErrorCodes(options, result); - - resolve(result.body); - } - } catch (error) { - reject(error); - } - }); -}; \ No newline at end of file +export const request = ( + config: OpenAPIConfig, + options: ApiRequestOptions, + axiosClient: AxiosInstance = axios, +): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options) + const formData = getFormData(options) + const body = getRequestBody(options) + const headers = await getHeaders(config, options) + + if (!onCancel.isCancelled) { + let response = await sendRequest( + config, + options, + url, + body, + formData, + headers, + onCancel, + axiosClient, + ) + + for (const fn of config.interceptors.response._fns) { + response = await fn(response) + } + + const responseBody = getResponseBody(response) + const responseHeader = getResponseHeader( + response, + options.responseHeader, + ) + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + } + + catchErrorCodes(options, result) + + resolve(result.body) + } + } catch (error) { + reject(error) + } + }) +} diff --git a/frontend/src/client/core/types.ts b/frontend/src/client/core/types.ts index 64ecde21e6..199c08d3df 100644 --- a/frontend/src/client/core/types.ts +++ b/frontend/src/client/core/types.ts @@ -1,12 +1,14 @@ -import type { ApiResult } from './ApiResult'; +import type { ApiResult } from "./ApiResult" -export type TResult = 'body' | 'raw'; +export type TResult = "body" | "raw" -export type TApiResponse = - Exclude extends never - ? ApiResult - : ApiResult['body']; +export type TApiResponse = Exclude< + T, + "raw" +> extends never + ? ApiResult + : ApiResult["body"] export type TConfig = { - _result?: T; -}; \ No newline at end of file + _result?: T +} diff --git a/frontend/src/client/index.ts b/frontend/src/client/index.ts index 322c51b40e..adf1d0cabf 100644 --- a/frontend/src/client/index.ts +++ b/frontend/src/client/index.ts @@ -1,9 +1,8 @@ +export { ApiError } from "./core/ApiError" +export { CancelablePromise, CancelError } from "./core/CancelablePromise" +export { OpenAPI } from "./core/OpenAPI" +export type { OpenAPIConfig } from "./core/OpenAPI" -export { ApiError } from './core/ApiError'; -export { CancelablePromise, CancelError } from './core/CancelablePromise'; -export { OpenAPI } from './core/OpenAPI'; -export type { OpenAPIConfig } from './core/OpenAPI'; - -export * from './models' -export * from './schemas' -export * from './services' +export * from "./models" +export * from "./schemas" +export * from "./services" diff --git a/frontend/src/client/models.ts b/frontend/src/client/models.ts index 6732a18638..a8e1b7ab28 100644 --- a/frontend/src/client/models.ts +++ b/frontend/src/client/models.ts @@ -1,132 +1,99 @@ export type Body_login_login_access_token = { - grant_type?: string | null; - username: string; - password: string; - scope?: string; - client_id?: string | null; - client_secret?: string | null; -}; - - + grant_type?: string | null + username: string + password: string + scope?: string + client_id?: string | null + client_secret?: string | null +} export type HTTPValidationError = { - detail?: Array; -}; - - + detail?: Array +} export type ItemCreate = { - title: string; - description?: string | null; -}; - - + title: string + description?: string | null +} export type ItemPublic = { - title: string; - description?: string | null; - id: number; - owner_id: number; -}; - - + title: string + description?: string | null + id: number + owner_id: number +} export type ItemUpdate = { - title?: string | null; - description?: string | null; -}; - - + title?: string | null + description?: string | null +} export type ItemsPublic = { - data: Array; - count: number; -}; - - + data: Array + count: number +} export type Message = { - message: string; -}; - - + message: string +} export type NewPassword = { - token: string; - new_password: string; -}; - - + token: string + new_password: string +} export type Token = { - access_token: string; - token_type?: string; -}; - - + access_token: string + token_type?: string +} export type UpdatePassword = { - current_password: string; - new_password: string; -}; - - + current_password: string + new_password: string +} export type UserCreate = { - email: string; - is_active?: boolean; - is_superuser?: boolean; - full_name?: string | null; - password: string; -}; - - + email: string + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + password: string +} export type UserPublic = { - email: string; - is_active?: boolean; - is_superuser?: boolean; - full_name?: string | null; - id: number; -}; - - + email: string + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + id: number +} export type UserRegister = { - email: string; - password: string; - full_name?: string | null; -}; - - + email: string + password: string + full_name?: string | null +} export type UserUpdate = { - email?: string | null; - is_active?: boolean; - is_superuser?: boolean; - full_name?: string | null; - password?: string | null; -}; - - + email?: string | null + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + password?: string | null +} export type UserUpdateMe = { - full_name?: string | null; - email?: string | null; -}; - - + full_name?: string | null + email?: string | null +} export type UsersPublic = { - data: Array; - count: number; -}; - - + data: Array + count: number +} export type ValidationError = { - loc: Array; - msg: string; - type: string; -}; - + loc: Array + msg: string + type: string +} diff --git a/frontend/src/client/schemas.ts b/frontend/src/client/schemas.ts index 82bceb59e9..0043c1ba13 100644 --- a/frontend/src/client/schemas.ts +++ b/frontend/src/client/schemas.ts @@ -1,357 +1,405 @@ export const $Body_login_login_access_token = { - properties: { - grant_type: { - type: 'any-of', - contains: [{ - type: 'string', - pattern: 'password', -}, { - type: 'null', -}], -}, - username: { - type: 'string', - isRequired: true, -}, - password: { - type: 'string', - isRequired: true, -}, - scope: { - type: 'string', - default: '', -}, - client_id: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - client_secret: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - }, -} as const; + properties: { + grant_type: { + type: "any-of", + contains: [ + { + type: "string", + pattern: "password", + }, + { + type: "null", + }, + ], + }, + username: { + type: "string", + isRequired: true, + }, + password: { + type: "string", + isRequired: true, + }, + scope: { + type: "string", + default: "", + }, + client_id: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + client_secret: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + }, +} as const export const $HTTPValidationError = { - properties: { - detail: { - type: 'array', - contains: { - type: 'ValidationError', - }, -}, - }, -} as const; + properties: { + detail: { + type: "array", + contains: { + type: "ValidationError", + }, + }, + }, +} as const export const $ItemCreate = { - properties: { - title: { - type: 'string', - isRequired: true, -}, - description: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - }, -} as const; + properties: { + title: { + type: "string", + isRequired: true, + }, + description: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + }, +} as const export const $ItemPublic = { - properties: { - title: { - type: 'string', - isRequired: true, -}, - description: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - id: { - type: 'number', - isRequired: true, -}, - owner_id: { - type: 'number', - isRequired: true, -}, - }, -} as const; + properties: { + title: { + type: "string", + isRequired: true, + }, + description: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + id: { + type: "number", + isRequired: true, + }, + owner_id: { + type: "number", + isRequired: true, + }, + }, +} as const export const $ItemUpdate = { - properties: { - title: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - description: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - }, -} as const; + properties: { + title: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + description: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + }, +} as const export const $ItemsPublic = { - properties: { - data: { - type: 'array', - contains: { - type: 'ItemPublic', - }, - isRequired: true, -}, - count: { - type: 'number', - isRequired: true, -}, - }, -} as const; + properties: { + data: { + type: "array", + contains: { + type: "ItemPublic", + }, + isRequired: true, + }, + count: { + type: "number", + isRequired: true, + }, + }, +} as const export const $Message = { - properties: { - message: { - type: 'string', - isRequired: true, -}, - }, -} as const; + properties: { + message: { + type: "string", + isRequired: true, + }, + }, +} as const export const $NewPassword = { - properties: { - token: { - type: 'string', - isRequired: true, -}, - new_password: { - type: 'string', - isRequired: true, -}, - }, -} as const; + properties: { + token: { + type: "string", + isRequired: true, + }, + new_password: { + type: "string", + isRequired: true, + }, + }, +} as const export const $Token = { - properties: { - access_token: { - type: 'string', - isRequired: true, -}, - token_type: { - type: 'string', - default: 'bearer', -}, - }, -} as const; + properties: { + access_token: { + type: "string", + isRequired: true, + }, + token_type: { + type: "string", + default: "bearer", + }, + }, +} as const export const $UpdatePassword = { - properties: { - current_password: { - type: 'string', - isRequired: true, -}, - new_password: { - type: 'string', - isRequired: true, -}, - }, -} as const; + properties: { + current_password: { + type: "string", + isRequired: true, + }, + new_password: { + type: "string", + isRequired: true, + }, + }, +} as const export const $UserCreate = { - properties: { - email: { - type: 'string', - isRequired: true, -}, - is_active: { - type: 'boolean', - default: true, -}, - is_superuser: { - type: 'boolean', - default: false, -}, - full_name: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - password: { - type: 'string', - isRequired: true, -}, - }, -} as const; + properties: { + email: { + type: "string", + isRequired: true, + }, + is_active: { + type: "boolean", + default: true, + }, + is_superuser: { + type: "boolean", + default: false, + }, + full_name: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + password: { + type: "string", + isRequired: true, + }, + }, +} as const export const $UserPublic = { - properties: { - email: { - type: 'string', - isRequired: true, -}, - is_active: { - type: 'boolean', - default: true, -}, - is_superuser: { - type: 'boolean', - default: false, -}, - full_name: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - id: { - type: 'number', - isRequired: true, -}, - }, -} as const; + properties: { + email: { + type: "string", + isRequired: true, + }, + is_active: { + type: "boolean", + default: true, + }, + is_superuser: { + type: "boolean", + default: false, + }, + full_name: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + id: { + type: "number", + isRequired: true, + }, + }, +} as const export const $UserRegister = { - properties: { - email: { - type: 'string', - isRequired: true, -}, - password: { - type: 'string', - isRequired: true, -}, - full_name: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - }, -} as const; + properties: { + email: { + type: "string", + isRequired: true, + }, + password: { + type: "string", + isRequired: true, + }, + full_name: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + }, +} as const export const $UserUpdate = { - properties: { - email: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - is_active: { - type: 'boolean', - default: true, -}, - is_superuser: { - type: 'boolean', - default: false, -}, - full_name: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - password: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - }, -} as const; + properties: { + email: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + is_active: { + type: "boolean", + default: true, + }, + is_superuser: { + type: "boolean", + default: false, + }, + full_name: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + password: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + }, +} as const export const $UserUpdateMe = { - properties: { - full_name: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - email: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'null', -}], -}, - }, -} as const; + properties: { + full_name: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + email: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "null", + }, + ], + }, + }, +} as const export const $UsersPublic = { - properties: { - data: { - type: 'array', - contains: { - type: 'UserPublic', - }, - isRequired: true, -}, - count: { - type: 'number', - isRequired: true, -}, - }, -} as const; + properties: { + data: { + type: "array", + contains: { + type: "UserPublic", + }, + isRequired: true, + }, + count: { + type: "number", + isRequired: true, + }, + }, +} as const export const $ValidationError = { - properties: { - loc: { - type: 'array', - contains: { - type: 'any-of', - contains: [{ - type: 'string', -}, { - type: 'number', -}], -}, - isRequired: true, -}, - msg: { - type: 'string', - isRequired: true, -}, - type: { - type: 'string', - isRequired: true, -}, - }, -} as const; \ No newline at end of file + properties: { + loc: { + type: "array", + contains: { + type: "any-of", + contains: [ + { + type: "string", + }, + { + type: "number", + }, + ], + }, + isRequired: true, + }, + msg: { + type: "string", + isRequired: true, + }, + type: { + type: "string", + isRequired: true, + }, + }, +} as const diff --git a/frontend/src/client/services.ts b/frontend/src/client/services.ts index 230b24d0c9..e4b8138f1a 100644 --- a/frontend/src/client/services.ts +++ b/frontend/src/client/services.ts @@ -1,537 +1,517 @@ -import type { CancelablePromise } from './core/CancelablePromise'; -import { OpenAPI } from './core/OpenAPI'; -import { request as __request } from './core/request'; - -import type { Body_login_login_access_token,Message,NewPassword,Token,UserPublic,UpdatePassword,UserCreate,UserRegister,UsersPublic,UserUpdate,UserUpdateMe,ItemCreate,ItemPublic,ItemsPublic,ItemUpdate } from './models'; +import type { CancelablePromise } from "./core/CancelablePromise" +import { OpenAPI } from "./core/OpenAPI" +import { request as __request } from "./core/request" + +import type { + Body_login_login_access_token, + Message, + NewPassword, + Token, + UserPublic, + UpdatePassword, + UserCreate, + UserRegister, + UsersPublic, + UserUpdate, + UserUpdateMe, + ItemCreate, + ItemPublic, + ItemsPublic, + ItemUpdate, +} from "./models" export type TDataLoginAccessToken = { - formData: Body_login_login_access_token - - } + formData: Body_login_login_access_token +} export type TDataRecoverPassword = { - email: string - - } + email: string +} export type TDataResetPassword = { - requestBody: NewPassword - - } + requestBody: NewPassword +} export type TDataRecoverPasswordHtmlContent = { - email: string - - } + email: string +} export class LoginService { - - /** - * Login Access Token - * OAuth2 compatible token login, get an access token for future requests - * @returns Token Successful Response - * @throws ApiError - */ - public static loginAccessToken(data: TDataLoginAccessToken): CancelablePromise { - const { -formData, -} = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/login/access-token', - formData: formData, - mediaType: 'application/x-www-form-urlencoded', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Test Token - * Test access token - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static testToken(): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/login/test-token', - }); - } - - /** - * Recover Password - * Password Recovery - * @returns Message Successful Response - * @throws ApiError - */ - public static recoverPassword(data: TDataRecoverPassword): CancelablePromise { - const { -email, -} = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/password-recovery/{email}', - path: { - email - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Reset Password - * Reset password - * @returns Message Successful Response - * @throws ApiError - */ - public static resetPassword(data: TDataResetPassword): CancelablePromise { - const { -requestBody, -} = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/reset-password/', - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Recover Password Html Content - * HTML Content for Password Recovery - * @returns string Successful Response - * @throws ApiError - */ - public static recoverPasswordHtmlContent(data: TDataRecoverPasswordHtmlContent): CancelablePromise { - const { -email, -} = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/password-recovery-html-content/{email}', - path: { - email - }, - errors: { - 422: `Validation Error`, - }, - }); - } - + /** + * Login Access Token + * OAuth2 compatible token login, get an access token for future requests + * @returns Token Successful Response + * @throws ApiError + */ + public static loginAccessToken( + data: TDataLoginAccessToken, + ): CancelablePromise { + const { formData } = data + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/login/access-token", + formData: formData, + mediaType: "application/x-www-form-urlencoded", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Test Token + * Test access token + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static testToken(): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/login/test-token", + }) + } + + /** + * Recover Password + * Password Recovery + * @returns Message Successful Response + * @throws ApiError + */ + public static recoverPassword( + data: TDataRecoverPassword, + ): CancelablePromise { + const { email } = data + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/password-recovery/{email}", + path: { + email, + }, + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Reset Password + * Reset password + * @returns Message Successful Response + * @throws ApiError + */ + public static resetPassword( + data: TDataResetPassword, + ): CancelablePromise { + const { requestBody } = data + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/reset-password/", + body: requestBody, + mediaType: "application/json", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Recover Password Html Content + * HTML Content for Password Recovery + * @returns string Successful Response + * @throws ApiError + */ + public static recoverPasswordHtmlContent( + data: TDataRecoverPasswordHtmlContent, + ): CancelablePromise { + const { email } = data + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/password-recovery-html-content/{email}", + path: { + email, + }, + errors: { + 422: `Validation Error`, + }, + }) + } } export type TDataReadUsers = { - limit?: number -skip?: number - - } + limit?: number + skip?: number +} export type TDataCreateUser = { - requestBody: UserCreate - - } + requestBody: UserCreate +} export type TDataUpdateUserMe = { - requestBody: UserUpdateMe - - } + requestBody: UserUpdateMe +} export type TDataUpdatePasswordMe = { - requestBody: UpdatePassword - - } + requestBody: UpdatePassword +} export type TDataRegisterUser = { - requestBody: UserRegister - - } + requestBody: UserRegister +} export type TDataReadUserById = { - userId: number - - } + userId: number +} export type TDataUpdateUser = { - requestBody: UserUpdate -userId: number - - } + requestBody: UserUpdate + userId: number +} export type TDataDeleteUser = { - userId: number - - } + userId: number +} export class UsersService { - - /** - * Read Users - * Retrieve users. - * @returns UsersPublic Successful Response - * @throws ApiError - */ - public static readUsers(data: TDataReadUsers = {}): CancelablePromise { - const { -limit = 100, -skip = 0, -} = data; - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/users/', - query: { - skip, limit - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Create User - * Create new user. - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static createUser(data: TDataCreateUser): CancelablePromise { - const { -requestBody, -} = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/users/', - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Read User Me - * Get current user. - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static readUserMe(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/users/me', - }); - } - - /** - * Delete User Me - * Delete own user. - * @returns Message Successful Response - * @throws ApiError - */ - public static deleteUserMe(): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/v1/users/me', - }); - } - - /** - * Update User Me - * Update own user. - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static updateUserMe(data: TDataUpdateUserMe): CancelablePromise { - const { -requestBody, -} = data; - return __request(OpenAPI, { - method: 'PATCH', - url: '/api/v1/users/me', - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Update Password Me - * Update own password. - * @returns Message Successful Response - * @throws ApiError - */ - public static updatePasswordMe(data: TDataUpdatePasswordMe): CancelablePromise { - const { -requestBody, -} = data; - return __request(OpenAPI, { - method: 'PATCH', - url: '/api/v1/users/me/password', - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Register User - * Create new user without the need to be logged in. - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static registerUser(data: TDataRegisterUser): CancelablePromise { - const { -requestBody, -} = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/users/signup', - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Read User By Id - * Get a specific user by id. - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static readUserById(data: TDataReadUserById): CancelablePromise { - const { -userId, -} = data; - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/users/{user_id}', - path: { - user_id: userId - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Update User - * Update a user. - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static updateUser(data: TDataUpdateUser): CancelablePromise { - const { -requestBody, -userId, -} = data; - return __request(OpenAPI, { - method: 'PATCH', - url: '/api/v1/users/{user_id}', - path: { - user_id: userId - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Delete User - * Delete a user. - * @returns Message Successful Response - * @throws ApiError - */ - public static deleteUser(data: TDataDeleteUser): CancelablePromise { - const { -userId, -} = data; - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/v1/users/{user_id}', - path: { - user_id: userId - }, - errors: { - 422: `Validation Error`, - }, - }); - } - + /** + * Read Users + * Retrieve users. + * @returns UsersPublic Successful Response + * @throws ApiError + */ + public static readUsers( + data: TDataReadUsers = {}, + ): CancelablePromise { + const { limit = 100, skip = 0 } = data + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/users/", + query: { + skip, + limit, + }, + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Create User + * Create new user. + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static createUser( + data: TDataCreateUser, + ): CancelablePromise { + const { requestBody } = data + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/users/", + body: requestBody, + mediaType: "application/json", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Read User Me + * Get current user. + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static readUserMe(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/users/me", + }) + } + + /** + * Delete User Me + * Delete own user. + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteUserMe(): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/api/v1/users/me", + }) + } + + /** + * Update User Me + * Update own user. + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static updateUserMe( + data: TDataUpdateUserMe, + ): CancelablePromise { + const { requestBody } = data + return __request(OpenAPI, { + method: "PATCH", + url: "/api/v1/users/me", + body: requestBody, + mediaType: "application/json", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Update Password Me + * Update own password. + * @returns Message Successful Response + * @throws ApiError + */ + public static updatePasswordMe( + data: TDataUpdatePasswordMe, + ): CancelablePromise { + const { requestBody } = data + return __request(OpenAPI, { + method: "PATCH", + url: "/api/v1/users/me/password", + body: requestBody, + mediaType: "application/json", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Register User + * Create new user without the need to be logged in. + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static registerUser( + data: TDataRegisterUser, + ): CancelablePromise { + const { requestBody } = data + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/users/signup", + body: requestBody, + mediaType: "application/json", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Read User By Id + * Get a specific user by id. + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static readUserById( + data: TDataReadUserById, + ): CancelablePromise { + const { userId } = data + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/users/{user_id}", + path: { + user_id: userId, + }, + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Update User + * Update a user. + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static updateUser( + data: TDataUpdateUser, + ): CancelablePromise { + const { requestBody, userId } = data + return __request(OpenAPI, { + method: "PATCH", + url: "/api/v1/users/{user_id}", + path: { + user_id: userId, + }, + body: requestBody, + mediaType: "application/json", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Delete User + * Delete a user. + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteUser(data: TDataDeleteUser): CancelablePromise { + const { userId } = data + return __request(OpenAPI, { + method: "DELETE", + url: "/api/v1/users/{user_id}", + path: { + user_id: userId, + }, + errors: { + 422: `Validation Error`, + }, + }) + } } export type TDataTestEmail = { - emailTo: string - - } + emailTo: string +} export class UtilsService { - - /** - * Test Email - * Test emails. - * @returns Message Successful Response - * @throws ApiError - */ - public static testEmail(data: TDataTestEmail): CancelablePromise { - const { -emailTo, -} = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/utils/test-email/', - query: { - email_to: emailTo - }, - errors: { - 422: `Validation Error`, - }, - }); - } - + /** + * Test Email + * Test emails. + * @returns Message Successful Response + * @throws ApiError + */ + public static testEmail(data: TDataTestEmail): CancelablePromise { + const { emailTo } = data + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/utils/test-email/", + query: { + email_to: emailTo, + }, + errors: { + 422: `Validation Error`, + }, + }) + } } export type TDataReadItems = { - limit?: number -skip?: number - - } + limit?: number + skip?: number +} export type TDataCreateItem = { - requestBody: ItemCreate - - } + requestBody: ItemCreate +} export type TDataReadItem = { - id: number - - } + id: number +} export type TDataUpdateItem = { - id: number -requestBody: ItemUpdate - - } + id: number + requestBody: ItemUpdate +} export type TDataDeleteItem = { - id: number - - } + id: number +} export class ItemsService { - - /** - * Read Items - * Retrieve items. - * @returns ItemsPublic Successful Response - * @throws ApiError - */ - public static readItems(data: TDataReadItems = {}): CancelablePromise { - const { -limit = 100, -skip = 0, -} = data; - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/items/', - query: { - skip, limit - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Create Item - * Create new item. - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static createItem(data: TDataCreateItem): CancelablePromise { - const { -requestBody, -} = data; - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/items/', - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Read Item - * Get item by ID. - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static readItem(data: TDataReadItem): CancelablePromise { - const { -id, -} = data; - return __request(OpenAPI, { - method: 'GET', - url: '/api/v1/items/{id}', - path: { - id - }, - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Update Item - * Update an item. - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static updateItem(data: TDataUpdateItem): CancelablePromise { - const { -id, -requestBody, -} = data; - return __request(OpenAPI, { - method: 'PUT', - url: '/api/v1/items/{id}', - path: { - id - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - - /** - * Delete Item - * Delete an item. - * @returns Message Successful Response - * @throws ApiError - */ - public static deleteItem(data: TDataDeleteItem): CancelablePromise { - const { -id, -} = data; - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/v1/items/{id}', - path: { - id - }, - errors: { - 422: `Validation Error`, - }, - }); - } - -} \ No newline at end of file + /** + * Read Items + * Retrieve items. + * @returns ItemsPublic Successful Response + * @throws ApiError + */ + public static readItems( + data: TDataReadItems = {}, + ): CancelablePromise { + const { limit = 100, skip = 0 } = data + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/items/", + query: { + skip, + limit, + }, + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Create Item + * Create new item. + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static createItem( + data: TDataCreateItem, + ): CancelablePromise { + const { requestBody } = data + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/items/", + body: requestBody, + mediaType: "application/json", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Read Item + * Get item by ID. + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static readItem(data: TDataReadItem): CancelablePromise { + const { id } = data + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/items/{id}", + path: { + id, + }, + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Update Item + * Update an item. + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static updateItem( + data: TDataUpdateItem, + ): CancelablePromise { + const { id, requestBody } = data + return __request(OpenAPI, { + method: "PUT", + url: "/api/v1/items/{id}", + path: { + id, + }, + body: requestBody, + mediaType: "application/json", + errors: { + 422: `Validation Error`, + }, + }) + } + + /** + * Delete Item + * Delete an item. + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteItem(data: TDataDeleteItem): CancelablePromise { + const { id } = data + return __request(OpenAPI, { + method: "DELETE", + url: "/api/v1/items/{id}", + path: { + id, + }, + errors: { + 422: `Validation Error`, + }, + }) + } +} From 7484aa33a75f18ef77bc3a0a765c40ed559c69b4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 27 Jun 2024 19:54:08 +0000 Subject: [PATCH 542/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index c308ac7656..c383d07a08 100644 --- a/release-notes.md +++ b/release-notes.md @@ -20,6 +20,7 @@ ### Refactors +* 🎨 Run biome after OpenAPI client generation. PR [#1226](https://github.com/tiangolo/full-stack-fastapi-template/pull/1226) by [@tomerb](https://github.com/tomerb). * ♻️ Update DeleteConfirmation component to use new service. PR [#1224](https://github.com/tiangolo/full-stack-fastapi-template/pull/1224) by [@alejsdev](https://github.com/alejsdev). * ♻️ Update client services. PR [#1223](https://github.com/tiangolo/full-stack-fastapi-template/pull/1223) by [@alejsdev](https://github.com/alejsdev). * ⚒️ Add minor frontend tweaks. PR [#1210](https://github.com/tiangolo/full-stack-fastapi-template/pull/1210) by [@alejsdev](https://github.com/alejsdev). From d52260598b3bb22bbe1f66f0ff622b440b732e5b Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:38:45 -0500 Subject: [PATCH 543/771] =?UTF-8?q?=F0=9F=8E=A8=20Format=20and=20lint=20(#?= =?UTF-8?q?1243)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/UserSettings/DeleteConfirmation.tsx | 2 +- frontend/src/routes/__root.tsx | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/UserSettings/DeleteConfirmation.tsx b/frontend/src/components/UserSettings/DeleteConfirmation.tsx index 87f11de292..7301d6894d 100644 --- a/frontend/src/components/UserSettings/DeleteConfirmation.tsx +++ b/frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -11,7 +11,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" import React from "react" import { useForm } from "react-hook-form" -import { UsersService, type ApiError } from "../../client" +import { type ApiError, UsersService } from "../../client" import useAuth from "../../hooks/useAuth" import useCustomToast from "../../hooks/useCustomToast" diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index c2ed565f6e..5da6383f2a 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -6,7 +6,7 @@ import NotFound from "../components/Common/NotFound" const loadDevtools = () => Promise.all([ import("@tanstack/router-devtools"), - import("@tanstack/react-query-devtools") + import("@tanstack/react-query-devtools"), ]).then(([routerDevtools, reactQueryDevtools]) => { return { default: () => ( @@ -14,14 +14,12 @@ const loadDevtools = () => - ) - }; - }); + ), + } + }) const TanStackDevtools = - process.env.NODE_ENV === "production" - ? () => null - : React.lazy(loadDevtools); + process.env.NODE_ENV === "production" ? () => null : React.lazy(loadDevtools) export const Route = createRootRoute({ component: () => ( From 84ed52f106a800439cc4e1dd24f5e639a3cebe4d Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 27 Jun 2024 21:39:05 +0000 Subject: [PATCH 544/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index c383d07a08..0a942fa9ee 100644 --- a/release-notes.md +++ b/release-notes.md @@ -20,6 +20,7 @@ ### Refactors +* 🎨 Format and lint . PR [#1243](https://github.com/tiangolo/full-stack-fastapi-template/pull/1243) by [@alejsdev](https://github.com/alejsdev). * 🎨 Run biome after OpenAPI client generation. PR [#1226](https://github.com/tiangolo/full-stack-fastapi-template/pull/1226) by [@tomerb](https://github.com/tomerb). * ♻️ Update DeleteConfirmation component to use new service. PR [#1224](https://github.com/tiangolo/full-stack-fastapi-template/pull/1224) by [@alejsdev](https://github.com/alejsdev). * ♻️ Update client services. PR [#1223](https://github.com/tiangolo/full-stack-fastapi-template/pull/1223) by [@alejsdev](https://github.com/alejsdev). From 594a6c6524b9d4b177ed666587f9e81746c74629 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:22:32 -0500 Subject: [PATCH 545/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20recover?= =?UTF-8?q?=20password=20(#1242)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/routes/recover-password.tsx | 31 ++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/frontend/src/routes/recover-password.tsx b/frontend/src/routes/recover-password.tsx index 7be649c861..6ea24bf37d 100644 --- a/frontend/src/routes/recover-password.tsx +++ b/frontend/src/routes/recover-password.tsx @@ -7,10 +7,11 @@ import { Input, Text, } from "@chakra-ui/react" +import { useMutation } from "@tanstack/react-query" import { createFileRoute, redirect } from "@tanstack/react-router" import { type SubmitHandler, useForm } from "react-hook-form" -import { LoginService } from "../client" +import { type ApiError, LoginService } from "../client" import { isLoggedIn } from "../hooks/useAuth" import useCustomToast from "../hooks/useCustomToast" import { emailPattern } from "../utils" @@ -34,19 +35,35 @@ function RecoverPassword() { const { register, handleSubmit, + reset, formState: { errors, isSubmitting }, } = useForm() const showToast = useCustomToast() - const onSubmit: SubmitHandler = async (data) => { + const recoverPassword = async (data: FormData) => { await LoginService.recoverPassword({ email: data.email, }) - showToast( - "Email sent.", - "We sent an email with a link to get back into your account.", - "success", - ) + } + + const mutation = useMutation({ + mutationFn: recoverPassword, + onSuccess: () => { + showToast( + "Email sent.", + "We sent an email with a link to get back into your account.", + "success", + ) + reset() + }, + onError: (err: ApiError) => { + const errDetail = (err.body as any)?.detail + showToast("Something went wrong.", `${errDetail}`, "error") + }, + }) + + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) } return ( From 8d04f5d4d9f97492a7eb127493d2a95543845967 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 28 Jun 2024 14:22:56 +0000 Subject: [PATCH 546/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 0a942fa9ee..a38b839c15 100644 --- a/release-notes.md +++ b/release-notes.md @@ -20,6 +20,7 @@ ### Refactors +* ♻️ Refactor recover password. PR [#1242](https://github.com/tiangolo/full-stack-fastapi-template/pull/1242) by [@alejsdev](https://github.com/alejsdev). * 🎨 Format and lint . PR [#1243](https://github.com/tiangolo/full-stack-fastapi-template/pull/1243) by [@alejsdev](https://github.com/alejsdev). * 🎨 Run biome after OpenAPI client generation. PR [#1226](https://github.com/tiangolo/full-stack-fastapi-template/pull/1226) by [@tomerb](https://github.com/tomerb). * ♻️ Update DeleteConfirmation component to use new service. PR [#1224](https://github.com/tiangolo/full-stack-fastapi-template/pull/1224) by [@alejsdev](https://github.com/alejsdev). From 0b174d7379c98733ff5dc2c2e2e2f85e82c76be5 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sun, 30 Jun 2024 03:05:25 +0200 Subject: [PATCH 547/771] =?UTF-8?q?=E2=9C=A8=20Add=20mailcatcher=20configu?= =?UTF-8?q?ration=20(#1244)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.override.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 7207ce110b..418b535ab6 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -62,6 +62,17 @@ services: INSTALL_DEV: ${INSTALL_DEV-true} # command: sleep infinity # Infinite loop to keep container alive doing nothing command: /start-reload.sh + environment: + SMTP_HOST: "mailcatcher" + SMTP_PORT: "1025" + SMTP_TLS: "false" + EMAILS_FROM_EMAIL: "noreply@example.com" + + mailcatcher: + image: schickling/mailcatcher + ports: + - "1080:1080" + - "1025:1025" frontend: restart: "no" From ddbfb32d5b8c7b239b9defff25677189fe8189b7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 30 Jun 2024 01:05:43 +0000 Subject: [PATCH 548/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index a38b839c15..8386ae889f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Add mailcatcher configuration. PR [#1244](https://github.com/tiangolo/full-stack-fastapi-template/pull/1244) by [@patrick91](https://github.com/patrick91). * ✨ Introduce pagination in items. PR [#1239](https://github.com/tiangolo/full-stack-fastapi-template/pull/1239) by [@patrick91](https://github.com/patrick91). * 🗃️ Add max_length validation for database models and input data. PR [#1233](https://github.com/tiangolo/full-stack-fastapi-template/pull/1233) by [@estebanx64](https://github.com/estebanx64). * ✨ Add TanStack React Query devtools in dev build. PR [#1217](https://github.com/tiangolo/full-stack-fastapi-template/pull/1217) by [@tomerb](https://github.com/tomerb). From 9288c0a38e8a69e15bc128eed04ac08a5e7ff944 Mon Sep 17 00:00:00 2001 From: jmondaud <35772177+jmondaud@users.noreply.github.com> Date: Thu, 18 Jul 2024 22:24:51 +0200 Subject: [PATCH 549/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20login.tsx?= =?UTF-8?q?=20to=20prevent=20error=20if=20username=20or=20password=20are?= =?UTF-8?q?=20empty=20(#1257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/routes/login.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 39b6dc7560..dd9ab407f9 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -88,6 +88,7 @@ function Login() { From 0d113def4a0eb279ad5e2a1c3db8ff03089399bd Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 18 Jul 2024 20:25:18 +0000 Subject: [PATCH 550/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 8386ae889f..9c7e26c059 100644 --- a/release-notes.md +++ b/release-notes.md @@ -21,6 +21,7 @@ ### Refactors +* ♻️ Update `login.tsx` to prevent error if username or password are empty. PR [#1257](https://github.com/tiangolo/full-stack-fastapi-template/pull/1257) by [@jmondaud](https://github.com/jmondaud). * ♻️ Refactor recover password. PR [#1242](https://github.com/tiangolo/full-stack-fastapi-template/pull/1242) by [@alejsdev](https://github.com/alejsdev). * 🎨 Format and lint . PR [#1243](https://github.com/tiangolo/full-stack-fastapi-template/pull/1243) by [@alejsdev](https://github.com/alejsdev). * 🎨 Run biome after OpenAPI client generation. PR [#1226](https://github.com/tiangolo/full-stack-fastapi-template/pull/1226) by [@tomerb](https://github.com/tomerb). From 19325b48c2ffad3ccbbfb607f583ba3388213b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Loureiro?= Date: Thu, 18 Jul 2024 21:27:39 +0100 Subject: [PATCH 551/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Abstraction=20of?= =?UTF-8?q?=20specific=20AddModal=20component=20out=20of=20the=20Navbar=20?= =?UTF-8?q?(#1246)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Common/Navbar.tsx | 17 ++++++++--------- frontend/src/routes/_layout/admin.tsx | 3 ++- frontend/src/routes/_layout/items.tsx | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Common/Navbar.tsx b/frontend/src/components/Common/Navbar.tsx index edcb3d08ac..1579a33b27 100644 --- a/frontend/src/components/Common/Navbar.tsx +++ b/frontend/src/components/Common/Navbar.tsx @@ -1,17 +1,17 @@ +import { ComponentType, ElementType } from 'react'; + import { Button, Flex, Icon, useDisclosure } from "@chakra-ui/react" import { FaPlus } from "react-icons/fa" -import AddUser from "../Admin/AddUser" -import AddItem from "../Items/AddItem" - interface NavbarProps { type: string + addModalAs: ComponentType | ElementType } -const Navbar = ({ type }: NavbarProps) => { - const addUserModal = useDisclosure() - const addItemModal = useDisclosure() +const Navbar = ({ type, addModalAs }: NavbarProps) => { + const addModal = useDisclosure() + const AddModal = addModalAs return ( <> @@ -26,12 +26,11 @@ const Navbar = ({ type }: NavbarProps) => { variant="primary" gap={1} fontSize={{ base: "sm", md: "inherit" }} - onClick={type === "User" ? addUserModal.onOpen : addItemModal.onOpen} + onClick={addModal.onOpen} > Add {type} - - + ) diff --git a/frontend/src/routes/_layout/admin.tsx b/frontend/src/routes/_layout/admin.tsx index 800317bf2b..b9724c4c19 100644 --- a/frontend/src/routes/_layout/admin.tsx +++ b/frontend/src/routes/_layout/admin.tsx @@ -20,6 +20,7 @@ import { Suspense } from "react" import { type UserPublic, UsersService } from "../../client" import ActionsMenu from "../../components/Common/ActionsMenu" import Navbar from "../../components/Common/Navbar" +import AddUser from "../../components/Admin/AddUser" export const Route = createFileRoute("/_layout/admin")({ component: Admin, @@ -93,7 +94,7 @@ function Admin() { User Management - + diff --git a/frontend/src/routes/_layout/items.tsx b/frontend/src/routes/_layout/items.tsx index 9946e3b4fd..fb66e2418d 100644 --- a/frontend/src/routes/_layout/items.tsx +++ b/frontend/src/routes/_layout/items.tsx @@ -20,6 +20,7 @@ import { useEffect } from "react" import { ItemsService } from "../../client" import ActionsMenu from "../../components/Common/ActionsMenu" import Navbar from "../../components/Common/Navbar" +import AddItem from "../../components/Items/AddItem" const itemsSearchSchema = z.object({ page: z.number().catch(1), @@ -135,7 +136,7 @@ function Items() { Items Management - + ) From 0f713ecadcf461ab3414887a6b728ea65d5ad65a Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 18 Jul 2024 20:27:56 +0000 Subject: [PATCH 552/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 9c7e26c059..d0dacde61d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -21,6 +21,7 @@ ### Refactors +* ♻️ Abstraction of specific AddModal component out of the Navbar. PR [#1246](https://github.com/tiangolo/full-stack-fastapi-template/pull/1246) by [@ajbloureiro](https://github.com/ajbloureiro). * ♻️ Update `login.tsx` to prevent error if username or password are empty. PR [#1257](https://github.com/tiangolo/full-stack-fastapi-template/pull/1257) by [@jmondaud](https://github.com/jmondaud). * ♻️ Refactor recover password. PR [#1242](https://github.com/tiangolo/full-stack-fastapi-template/pull/1242) by [@alejsdev](https://github.com/alejsdev). * 🎨 Format and lint . PR [#1243](https://github.com/tiangolo/full-stack-fastapi-template/pull/1243) by [@alejsdev](https://github.com/alejsdev). From 203711b4143c67dcab3af00b4450cfc819e7b4d9 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Sun, 21 Jul 2024 19:33:50 -0500 Subject: [PATCH 553/771] =?UTF-8?q?=F0=9F=8E=A8=20Format=20frontend=20(#12?= =?UTF-8?q?62)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/biome.json | 2 +- frontend/src/components/Common/Navbar.tsx | 2 +- frontend/src/routes/_layout/admin.tsx | 4 ++-- frontend/src/routes/_layout/items.tsx | 6 +++--- frontend/src/routes/login.tsx | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/biome.json b/frontend/biome.json index 1c2adb8db0..b6e397eb49 100644 --- a/frontend/biome.json +++ b/frontend/biome.json @@ -4,7 +4,7 @@ "enabled": true }, "files": { - "ignore": ["node_modules", "src/routeTree.gen.ts"] + "ignore": ["node_modules", "src/client/", "src/routeTree.gen.ts"] }, "linter": { "enabled": true, diff --git a/frontend/src/components/Common/Navbar.tsx b/frontend/src/components/Common/Navbar.tsx index 1579a33b27..2aba31c362 100644 --- a/frontend/src/components/Common/Navbar.tsx +++ b/frontend/src/components/Common/Navbar.tsx @@ -1,4 +1,4 @@ -import { ComponentType, ElementType } from 'react'; +import type { ComponentType, ElementType } from "react" import { Button, Flex, Icon, useDisclosure } from "@chakra-ui/react" import { FaPlus } from "react-icons/fa" diff --git a/frontend/src/routes/_layout/admin.tsx b/frontend/src/routes/_layout/admin.tsx index b9724c4c19..9ecddadf93 100644 --- a/frontend/src/routes/_layout/admin.tsx +++ b/frontend/src/routes/_layout/admin.tsx @@ -18,9 +18,9 @@ import { createFileRoute } from "@tanstack/react-router" import { Suspense } from "react" import { type UserPublic, UsersService } from "../../client" +import AddUser from "../../components/Admin/AddUser" import ActionsMenu from "../../components/Common/ActionsMenu" import Navbar from "../../components/Common/Navbar" -import AddUser from "../../components/Admin/AddUser" export const Route = createFileRoute("/_layout/admin")({ component: Admin, @@ -94,7 +94,7 @@ function Admin() { User Management - +
diff --git a/frontend/src/routes/_layout/items.tsx b/frontend/src/routes/_layout/items.tsx index fb66e2418d..4b618c127e 100644 --- a/frontend/src/routes/_layout/items.tsx +++ b/frontend/src/routes/_layout/items.tsx @@ -1,4 +1,3 @@ -import { z } from "zod" import { Button, Container, @@ -15,8 +14,9 @@ import { } from "@chakra-ui/react" import { useQuery, useQueryClient } from "@tanstack/react-query" import { createFileRoute, useNavigate } from "@tanstack/react-router" - import { useEffect } from "react" +import { z } from "zod" + import { ItemsService } from "../../client" import ActionsMenu from "../../components/Common/ActionsMenu" import Navbar from "../../components/Common/Navbar" @@ -136,7 +136,7 @@ function Items() { Items Management - + ) diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index dd9ab407f9..2c35208c71 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -103,7 +103,7 @@ function Login() { Date: Mon, 22 Jul 2024 00:34:10 +0000 Subject: [PATCH 554/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index d0dacde61d..4992291d8c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -21,6 +21,7 @@ ### Refactors +* 🎨 Format frontend. PR [#1262](https://github.com/tiangolo/full-stack-fastapi-template/pull/1262) by [@alejsdev](https://github.com/alejsdev). * ♻️ Abstraction of specific AddModal component out of the Navbar. PR [#1246](https://github.com/tiangolo/full-stack-fastapi-template/pull/1246) by [@ajbloureiro](https://github.com/ajbloureiro). * ♻️ Update `login.tsx` to prevent error if username or password are empty. PR [#1257](https://github.com/tiangolo/full-stack-fastapi-template/pull/1257) by [@jmondaud](https://github.com/jmondaud). * ♻️ Refactor recover password. PR [#1242](https://github.com/tiangolo/full-stack-fastapi-template/pull/1242) by [@alejsdev](https://github.com/alejsdev). From 3508e7bd67a6149d413ab4a080e93570864d58fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20=C3=81lvarez?= Date: Mon, 22 Jul 2024 02:36:05 +0200 Subject: [PATCH 555/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Replace=20deprecat?= =?UTF-8?q?ed=20utcnow()=20with=20now(timezone.utc)=20in=20utils=20module?= =?UTF-8?q?=20(#1247)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/core/security.py | 4 ++-- backend/app/utils.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/app/core/security.py b/backend/app/core/security.py index 58da9d9000..7aff7cfb32 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Any import jwt @@ -13,7 +13,7 @@ def create_access_token(subject: str | Any, expires_delta: timedelta) -> str: - expire = datetime.utcnow() + expires_delta + expire = datetime.now(timezone.utc) + expires_delta to_encode = {"exp": expire, "sub": str(subject)} encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt diff --git a/backend/app/utils.py b/backend/app/utils.py index a3c7e3aee1..d5ccf3153f 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -1,6 +1,6 @@ import logging from dataclasses import dataclass -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any @@ -98,7 +98,7 @@ def generate_new_account_email( def generate_password_reset_token(email: str) -> str: delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS) - now = datetime.utcnow() + now = datetime.now(timezone.utc) expires = now + delta exp = expires.timestamp() encoded_jwt = jwt.encode( From 1fcdffca57ba77f3543a186613154cd2a42d41e6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jul 2024 00:36:26 +0000 Subject: [PATCH 556/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 4992291d8c..635f336722 100644 --- a/release-notes.md +++ b/release-notes.md @@ -21,6 +21,7 @@ ### Refactors +* ♻️ Replace deprecated utcnow() with now(timezone.utc) in utils module. PR [#1247](https://github.com/tiangolo/full-stack-fastapi-template/pull/1247) by [@jalvarezz13](https://github.com/jalvarezz13). * 🎨 Format frontend. PR [#1262](https://github.com/tiangolo/full-stack-fastapi-template/pull/1262) by [@alejsdev](https://github.com/alejsdev). * ♻️ Abstraction of specific AddModal component out of the Navbar. PR [#1246](https://github.com/tiangolo/full-stack-fastapi-template/pull/1246) by [@ajbloureiro](https://github.com/ajbloureiro). * ♻️ Update `login.tsx` to prevent error if username or password are empty. PR [#1257](https://github.com/tiangolo/full-stack-fastapi-template/pull/1257) by [@jmondaud](https://github.com/jmondaud). From 7dccd06f4771048f6ecd3ed9d188ac2209b5ebbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Jul 2024 19:55:45 -0500 Subject: [PATCH 557/771] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20urllib3=20f?= =?UTF-8?q?rom=202.2.1=20to=202.2.2=20in=20/backend=20(#1235)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.2.1...2.2.2) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index aa96ea8ea3..6d0f085fc5 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1739,13 +1739,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] From bda2b7a18dd0f43e4d6341b6d21130a59ee2b9b8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jul 2024 00:56:04 +0000 Subject: [PATCH 558/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 635f336722..55fd4a4560 100644 --- a/release-notes.md +++ b/release-notes.md @@ -94,6 +94,7 @@ ### Internal +* ⬆️ Bump urllib3 from 2.2.1 to 2.2.2 in /backend. PR [#1235](https://github.com/tiangolo/full-stack-fastapi-template/pull/1235) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Ignore `src/routeTree.gen.ts` in biome. PR [#1175](https://github.com/tiangolo/full-stack-fastapi-template/pull/1175) by [@patrick91](https://github.com/patrick91). * 👷 Update Smokeshow download artifact GitHub Action. PR [#1198](https://github.com/tiangolo/full-stack-fastapi-template/pull/1198) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update Node.js version in `.nvmrc`. PR [#1192](https://github.com/tiangolo/full-stack-fastapi-template/pull/1192) by [@alejsdev](https://github.com/alejsdev). From bf16562301000ee8c74a34932cd229f6b0033cdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Jul 2024 19:56:19 -0500 Subject: [PATCH 559/771] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20certifi=20f?= =?UTF-8?q?rom=202024.2.2=20to=202024.7.4=20in=20/backend=20(#1250)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.2.2 to 2024.7.4. - [Commits](https://github.com/certifi/python-certifi/compare/2024.02.02...2024.07.04) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index 6d0f085fc5..5706001469 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -99,13 +99,13 @@ files = [ [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] From 5d7de16ad3c7b7df42727e69f0397de3747fc4e0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jul 2024 00:56:37 +0000 Subject: [PATCH 560/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 55fd4a4560..66ef8fe9d3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -94,6 +94,7 @@ ### Internal +* ⬆️ Bump certifi from 2024.2.2 to 2024.7.4 in /backend. PR [#1250](https://github.com/tiangolo/full-stack-fastapi-template/pull/1250) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆️ Bump urllib3 from 2.2.1 to 2.2.2 in /backend. PR [#1235](https://github.com/tiangolo/full-stack-fastapi-template/pull/1235) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Ignore `src/routeTree.gen.ts` in biome. PR [#1175](https://github.com/tiangolo/full-stack-fastapi-template/pull/1175) by [@patrick91](https://github.com/patrick91). * 👷 Update Smokeshow download artifact GitHub Action. PR [#1198](https://github.com/tiangolo/full-stack-fastapi-template/pull/1198) by [@tiangolo](https://github.com/tiangolo). From f199c792345a8fd7d89bd2e8b135aaffab87ceaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Jul 2024 19:56:48 -0500 Subject: [PATCH 561/771] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20setuptools?= =?UTF-8?q?=20from=2069.1.1=20to=2070.0.0=20in=20/backend=20(#1255)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [setuptools](https://github.com/pypa/setuptools) from 69.1.1 to 70.0.0. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v69.1.1...v70.0.0) --- updated-dependencies: - dependency-name: setuptools dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/poetry.lock | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index 5706001469..20c7e64589 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1524,19 +1524,18 @@ tornado = ["tornado (>=5)"] [[package]] name = "setuptools" -version = "69.1.1" +version = "70.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, - {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, + {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, + {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" From 4b5056026fd55b5f400d1c72885c3b445d4fdcbd Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jul 2024 00:57:06 +0000 Subject: [PATCH 562/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 66ef8fe9d3..19c5d26a96 100644 --- a/release-notes.md +++ b/release-notes.md @@ -94,6 +94,7 @@ ### Internal +* ⬆️ Bump setuptools from 69.1.1 to 70.0.0 in /backend. PR [#1255](https://github.com/tiangolo/full-stack-fastapi-template/pull/1255) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆️ Bump certifi from 2024.2.2 to 2024.7.4 in /backend. PR [#1250](https://github.com/tiangolo/full-stack-fastapi-template/pull/1250) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆️ Bump urllib3 from 2.2.1 to 2.2.2 in /backend. PR [#1235](https://github.com/tiangolo/full-stack-fastapi-template/pull/1235) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Ignore `src/routeTree.gen.ts` in biome. PR [#1175](https://github.com/tiangolo/full-stack-fastapi-template/pull/1175) by [@patrick91](https://github.com/patrick91). From 3673cfa4b4338e61a05ce5bf59b3fa54962b7a34 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:24:32 -0500 Subject: [PATCH 563/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20form=20in?= =?UTF-8?q?puts=20width=20(#1263)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/UserSettings/ChangePassword.tsx | 3 +++ frontend/src/components/UserSettings/UserInformation.tsx | 2 ++ 2 files changed, 5 insertions(+) diff --git a/frontend/src/components/UserSettings/ChangePassword.tsx b/frontend/src/components/UserSettings/ChangePassword.tsx index 9619b4f091..0698e1a9c8 100644 --- a/frontend/src/components/UserSettings/ChangePassword.tsx +++ b/frontend/src/components/UserSettings/ChangePassword.tsx @@ -71,6 +71,7 @@ const ChangePassword = () => { {...register("current_password")} placeholder="Password" type="password" + w="auto" /> {errors.current_password && ( @@ -85,6 +86,7 @@ const ChangePassword = () => { {...register("new_password", passwordRules())} placeholder="Password" type="password" + w="auto" /> {errors.new_password && ( {errors.new_password.message} @@ -97,6 +99,7 @@ const ChangePassword = () => { {...register("confirm_password", confirmPasswordRules(getValues))} placeholder="Password" type="password" + w="auto" /> {errors.confirm_password && ( diff --git a/frontend/src/components/UserSettings/UserInformation.tsx b/frontend/src/components/UserSettings/UserInformation.tsx index ca06b5a65e..03e2fdfb15 100644 --- a/frontend/src/components/UserSettings/UserInformation.tsx +++ b/frontend/src/components/UserSettings/UserInformation.tsx @@ -97,6 +97,7 @@ const UserInformation = () => { {...register("full_name", { maxLength: 30 })} type="text" size="md" + w="auto" /> ) : ( { })} type="email" size="md" + w="auto" /> ) : ( From b1a346a8f65348dfd1642435d55826744667423b Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jul 2024 01:24:51 +0000 Subject: [PATCH 564/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 19c5d26a96..398badd114 100644 --- a/release-notes.md +++ b/release-notes.md @@ -21,6 +21,7 @@ ### Refactors +* ♻️ Update form inputs width. PR [#1263](https://github.com/tiangolo/full-stack-fastapi-template/pull/1263) by [@alejsdev](https://github.com/alejsdev). * ♻️ Replace deprecated utcnow() with now(timezone.utc) in utils module. PR [#1247](https://github.com/tiangolo/full-stack-fastapi-template/pull/1247) by [@jalvarezz13](https://github.com/jalvarezz13). * 🎨 Format frontend. PR [#1262](https://github.com/tiangolo/full-stack-fastapi-template/pull/1262) by [@alejsdev](https://github.com/alejsdev). * ♻️ Abstraction of specific AddModal component out of the Navbar. PR [#1246](https://github.com/tiangolo/full-stack-fastapi-template/pull/1246) by [@ajbloureiro](https://github.com/ajbloureiro). From 14a91d97c7355b00b7fad54d9ce890cd642e39aa Mon Sep 17 00:00:00 2001 From: Esteban Maya Date: Mon, 22 Jul 2024 17:49:02 -0500 Subject: [PATCH 565/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Edit=20refactor=20?= =?UTF-8?q?db=20models=20to=20use=20UUID's=20instead=20of=20integer=20ID's?= =?UTF-8?q?=20(#1259)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- ...edit_replace_id_integers_in_all_models_.py | 90 ++ backend/app/api/routes/items.py | 13 +- backend/app/api/routes/users.py | 7 +- backend/app/crud.py | 3 +- backend/app/models.py | 16 +- backend/app/tests/api/routes/test_items.py | 16 +- backend/app/tests/api/routes/test_users.py | 7 +- backend/poetry.lock | 1153 +++++++++-------- backend/pyproject.toml | 2 +- 9 files changed, 746 insertions(+), 561 deletions(-) create mode 100755 backend/app/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py diff --git a/backend/app/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py b/backend/app/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py new file mode 100755 index 0000000000..37af1fa215 --- /dev/null +++ b/backend/app/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py @@ -0,0 +1,90 @@ +"""Edit replace id integers in all models to use UUID instead + +Revision ID: d98dd8ec85a3 +Revises: 9c0a54914c78 +Create Date: 2024-07-19 04:08:04.000976 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision = 'd98dd8ec85a3' +down_revision = '9c0a54914c78' +branch_labels = None +depends_on = None + + +def upgrade(): + # Ensure uuid-ossp extension is available + op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"') + + # Create a new UUID column with a default UUID value + op.add_column('user', sa.Column('new_id', postgresql.UUID(as_uuid=True), default=sa.text('uuid_generate_v4()'))) + op.add_column('item', sa.Column('new_id', postgresql.UUID(as_uuid=True), default=sa.text('uuid_generate_v4()'))) + op.add_column('item', sa.Column('new_owner_id', postgresql.UUID(as_uuid=True), nullable=True)) + + # Populate the new columns with UUIDs + op.execute('UPDATE "user" SET new_id = uuid_generate_v4()') + op.execute('UPDATE item SET new_id = uuid_generate_v4()') + op.execute('UPDATE item SET new_owner_id = (SELECT new_id FROM "user" WHERE "user".id = item.owner_id)') + + # Set the new_id as not nullable + op.alter_column('user', 'new_id', nullable=False) + op.alter_column('item', 'new_id', nullable=False) + + # Drop old columns and rename new columns + op.drop_constraint('item_owner_id_fkey', 'item', type_='foreignkey') + op.drop_column('item', 'owner_id') + op.alter_column('item', 'new_owner_id', new_column_name='owner_id') + + op.drop_column('user', 'id') + op.alter_column('user', 'new_id', new_column_name='id') + + op.drop_column('item', 'id') + op.alter_column('item', 'new_id', new_column_name='id') + + # Create primary key constraint + op.create_primary_key('user_pkey', 'user', ['id']) + op.create_primary_key('item_pkey', 'item', ['id']) + + # Recreate foreign key constraint + op.create_foreign_key('item_owner_id_fkey', 'item', 'user', ['owner_id'], ['id']) + +def downgrade(): + # Reverse the upgrade process + op.add_column('user', sa.Column('old_id', sa.Integer, autoincrement=True)) + op.add_column('item', sa.Column('old_id', sa.Integer, autoincrement=True)) + op.add_column('item', sa.Column('old_owner_id', sa.Integer, nullable=True)) + + # Populate the old columns with default values + # Generate sequences for the integer IDs if not exist + op.execute('CREATE SEQUENCE IF NOT EXISTS user_id_seq AS INTEGER OWNED BY "user".old_id') + op.execute('CREATE SEQUENCE IF NOT EXISTS item_id_seq AS INTEGER OWNED BY item.old_id') + + op.execute('SELECT setval(\'user_id_seq\', COALESCE((SELECT MAX(old_id) + 1 FROM "user"), 1), false)') + op.execute('SELECT setval(\'item_id_seq\', COALESCE((SELECT MAX(old_id) + 1 FROM item), 1), false)') + + op.execute('UPDATE "user" SET old_id = nextval(\'user_id_seq\')') + op.execute('UPDATE item SET old_id = nextval(\'item_id_seq\'), old_owner_id = (SELECT old_id FROM "user" WHERE "user".id = item.owner_id)') + + # Drop new columns and rename old columns back + op.drop_constraint('item_owner_id_fkey', 'item', type_='foreignkey') + op.drop_column('item', 'owner_id') + op.alter_column('item', 'old_owner_id', new_column_name='owner_id') + + op.drop_column('user', 'id') + op.alter_column('user', 'old_id', new_column_name='id') + + op.drop_column('item', 'id') + op.alter_column('item', 'old_id', new_column_name='id') + + # Create primary key constraint + op.create_primary_key('user_pkey', 'user', ['id']) + op.create_primary_key('item_pkey', 'item', ['id']) + + # Recreate foreign key constraint + op.create_foreign_key('item_owner_id_fkey', 'item', 'user', ['owner_id'], ['id']) diff --git a/backend/app/api/routes/items.py b/backend/app/api/routes/items.py index 7e06879961..67196c2366 100644 --- a/backend/app/api/routes/items.py +++ b/backend/app/api/routes/items.py @@ -1,3 +1,4 @@ +import uuid from typing import Any from fastapi import APIRouter, HTTPException @@ -41,7 +42,7 @@ def read_items( @router.get("/{id}", response_model=ItemPublic) -def read_item(session: SessionDep, current_user: CurrentUser, id: int) -> Any: +def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) -> Any: """ Get item by ID. """ @@ -69,7 +70,11 @@ def create_item( @router.put("/{id}", response_model=ItemPublic) def update_item( - *, session: SessionDep, current_user: CurrentUser, id: int, item_in: ItemUpdate + *, + session: SessionDep, + current_user: CurrentUser, + id: uuid.UUID, + item_in: ItemUpdate, ) -> Any: """ Update an item. @@ -88,7 +93,9 @@ def update_item( @router.delete("/{id}") -def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> Message: +def delete_item( + session: SessionDep, current_user: CurrentUser, id: uuid.UUID +) -> Message: """ Delete an item. """ diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 7a4066a8d3..21c30f1a41 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -1,3 +1,4 @@ +import uuid from typing import Any from fastapi import APIRouter, Depends, HTTPException @@ -163,7 +164,7 @@ def register_user(session: SessionDep, user_in: UserRegister) -> Any: @router.get("/{user_id}", response_model=UserPublic) def read_user_by_id( - user_id: int, session: SessionDep, current_user: CurrentUser + user_id: uuid.UUID, session: SessionDep, current_user: CurrentUser ) -> Any: """ Get a specific user by id. @@ -187,7 +188,7 @@ def read_user_by_id( def update_user( *, session: SessionDep, - user_id: int, + user_id: uuid.UUID, user_in: UserUpdate, ) -> Any: """ @@ -213,7 +214,7 @@ def update_user( @router.delete("/{user_id}", dependencies=[Depends(get_current_active_superuser)]) def delete_user( - session: SessionDep, current_user: CurrentUser, user_id: int + session: SessionDep, current_user: CurrentUser, user_id: uuid.UUID ) -> Message: """ Delete a user. diff --git a/backend/app/crud.py b/backend/app/crud.py index 405482af36..905bf48724 100644 --- a/backend/app/crud.py +++ b/backend/app/crud.py @@ -1,3 +1,4 @@ +import uuid from typing import Any from sqlmodel import Session, select @@ -45,7 +46,7 @@ def authenticate(*, session: Session, email: str, password: str) -> User | None: return db_user -def create_item(*, session: Session, item_in: ItemCreate, owner_id: int) -> Item: +def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item: db_item = Item.model_validate(item_in, update={"owner_id": owner_id}) session.add(db_item) session.commit() diff --git a/backend/app/models.py b/backend/app/models.py index be92454e94..e649746a44 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -1,3 +1,5 @@ +import uuid + from pydantic import EmailStr from sqlmodel import Field, Relationship, SQLModel @@ -39,14 +41,14 @@ class UpdatePassword(SQLModel): # Database model, database table inferred from class name class User(UserBase, table=True): - id: int | None = Field(default=None, primary_key=True) + id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) hashed_password: str items: list["Item"] = Relationship(back_populates="owner") # Properties to return via API, id is always required class UserPublic(UserBase): - id: int + id: uuid.UUID class UsersPublic(SQLModel): @@ -72,16 +74,16 @@ class ItemUpdate(ItemBase): # Database model, database table inferred from class name class Item(ItemBase, table=True): - id: int | None = Field(default=None, primary_key=True) + id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) title: str = Field(max_length=255) - owner_id: int | None = Field(default=None, foreign_key="user.id", nullable=False) + owner_id: uuid.UUID = Field(foreign_key="user.id", nullable=False) owner: User | None = Relationship(back_populates="items") # Properties to return via API, id is always required class ItemPublic(ItemBase): - id: int - owner_id: int + id: uuid.UUID + owner_id: uuid.UUID class ItemsPublic(SQLModel): @@ -102,7 +104,7 @@ class Token(SQLModel): # Contents of JWT token class TokenPayload(SQLModel): - sub: int | None = None + sub: str | None = None class NewPassword(SQLModel): diff --git a/backend/app/tests/api/routes/test_items.py b/backend/app/tests/api/routes/test_items.py index a47b358e57..c215238a69 100644 --- a/backend/app/tests/api/routes/test_items.py +++ b/backend/app/tests/api/routes/test_items.py @@ -1,3 +1,5 @@ +import uuid + from fastapi.testclient import TestClient from sqlmodel import Session @@ -34,15 +36,15 @@ def test_read_item( content = response.json() assert content["title"] == item.title assert content["description"] == item.description - assert content["id"] == item.id - assert content["owner_id"] == item.owner_id + assert content["id"] == str(item.id) + assert content["owner_id"] == str(item.owner_id) def test_read_item_not_found( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: response = client.get( - f"{settings.API_V1_STR}/items/999", + f"{settings.API_V1_STR}/items/{uuid.uuid4()}", headers=superuser_token_headers, ) assert response.status_code == 404 @@ -91,8 +93,8 @@ def test_update_item( content = response.json() assert content["title"] == data["title"] assert content["description"] == data["description"] - assert content["id"] == item.id - assert content["owner_id"] == item.owner_id + assert content["id"] == str(item.id) + assert content["owner_id"] == str(item.owner_id) def test_update_item_not_found( @@ -100,7 +102,7 @@ def test_update_item_not_found( ) -> None: data = {"title": "Updated title", "description": "Updated description"} response = client.put( - f"{settings.API_V1_STR}/items/999", + f"{settings.API_V1_STR}/items/{uuid.uuid4()}", headers=superuser_token_headers, json=data, ) @@ -141,7 +143,7 @@ def test_delete_item_not_found( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: response = client.delete( - f"{settings.API_V1_STR}/items/999", + f"{settings.API_V1_STR}/items/{uuid.uuid4()}", headers=superuser_token_headers, ) assert response.status_code == 404 diff --git a/backend/app/tests/api/routes/test_users.py b/backend/app/tests/api/routes/test_users.py index 5de077d2e9..d5daf9e934 100644 --- a/backend/app/tests/api/routes/test_users.py +++ b/backend/app/tests/api/routes/test_users.py @@ -1,3 +1,4 @@ +import uuid from unittest.mock import patch from fastapi.testclient import TestClient @@ -105,7 +106,7 @@ def test_get_existing_user_permissions_error( client: TestClient, normal_user_token_headers: dict[str, str] ) -> None: r = client.get( - f"{settings.API_V1_STR}/users/999999", + f"{settings.API_V1_STR}/users/{uuid.uuid4()}", headers=normal_user_token_headers, ) assert r.status_code == 403 @@ -371,7 +372,7 @@ def test_update_user_not_exists( ) -> None: data = {"full_name": "Updated_full_name"} r = client.patch( - f"{settings.API_V1_STR}/users/99999999", + f"{settings.API_V1_STR}/users/{uuid.uuid4()}", headers=superuser_token_headers, json=data, ) @@ -468,7 +469,7 @@ def test_delete_user_not_found( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: r = client.delete( - f"{settings.API_V1_STR}/users/99999999", + f"{settings.API_V1_STR}/users/{uuid.uuid4()}", headers=superuser_token_headers, ) assert r.status_code == 404 diff --git a/backend/poetry.lock b/backend/poetry.lock index 20c7e64589..af32d87a20 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "alembic" -version = "1.13.1" +version = "1.13.2" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" files = [ - {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, - {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, + {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, + {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, ] [package.dependencies] @@ -21,24 +21,24 @@ tz = ["backports.zoneinfo"] [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] name = "anyio" -version = "4.3.0" +version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, - {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, ] [package.dependencies] @@ -88,13 +88,13 @@ typecheck = ["mypy"] [[package]] name = "cachetools" -version = "5.3.3" +version = "5.4.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, + {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, + {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, ] [[package]] @@ -256,63 +256,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.3" +version = "7.6.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, - {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, - {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, - {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, - {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, - {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, - {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, - {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, - {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, - {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, - {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, - {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, - {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, - {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.extras] @@ -331,18 +331,21 @@ files = [ [[package]] name = "cssutils" -version = "2.9.0" +version = "2.11.1" description = "A CSS Cascading Style Sheets library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "cssutils-2.9.0-py3-none-any.whl", hash = "sha256:f8b013169e281c0c6083207366c5005f5dd4549055f7aba840384fb06a78745c"}, - {file = "cssutils-2.9.0.tar.gz", hash = "sha256:89477b3d17d790e97b9fb4def708767061055795aae6f7c82ae32e967c9be4cd"}, + {file = "cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1"}, + {file = "cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2"}, ] +[package.dependencies] +more-itertools = "*" + [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "distlib" @@ -377,13 +380,13 @@ wmi = ["wmi (>=1.5.1)"] [[package]] name = "email-validator" -version = "2.1.1" +version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" files = [ - {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"}, - {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"}, + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, ] [package.dependencies] @@ -411,13 +414,13 @@ requests = "*" [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -444,18 +447,18 @@ all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" [[package]] name = "filelock" -version = "3.13.1" +version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] @@ -563,13 +566,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.4" +version = "1.0.5" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, - {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, ] [package.dependencies] @@ -580,7 +583,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.25.0)"] +trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httptools" @@ -656,13 +659,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.5.35" +version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, ] [package.extras] @@ -709,106 +712,171 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "lxml" -version = "5.1.0" +version = "5.2.2" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" files = [ - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, - {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, - {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, - {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, - {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, - {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, - {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, - {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, - {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, - {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, - {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, - {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, - {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, - {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, - {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, - {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, - {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, - {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632"}, + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526"}, + {file = "lxml-5.2.2-cp310-cp310-win32.whl", hash = "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30"}, + {file = "lxml-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b"}, + {file = "lxml-5.2.2-cp311-cp311-win32.whl", hash = "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438"}, + {file = "lxml-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836"}, + {file = "lxml-5.2.2-cp312-cp312-win32.whl", hash = "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a"}, + {file = "lxml-5.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48"}, + {file = "lxml-5.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264"}, + {file = "lxml-5.2.2-cp36-cp36m-win32.whl", hash = "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3"}, + {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, + {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, + {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, + {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, + {file = "lxml-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1"}, + {file = "lxml-5.2.2-cp38-cp38-win32.whl", hash = "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30"}, + {file = "lxml-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9"}, + {file = "lxml-5.2.2-cp39-cp39-win32.whl", hash = "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf"}, + {file = "lxml-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324"}, + {file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.7)"] +source = ["Cython (>=3.0.10)"] [[package]] name = "mako" -version = "1.3.2" +version = "1.3.5" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" files = [ - {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, - {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, + {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, + {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, ] [package.dependencies] @@ -888,40 +956,51 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "more-itertools" +version = "10.3.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, + {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, +] + [[package]] name = "mypy" -version = "1.9.0" +version = "1.10.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, + {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, + {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, + {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, + {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, + {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, + {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, + {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, + {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, + {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, + {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, + {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, + {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, + {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, + {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, + {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, + {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, + {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, ] [package.dependencies] @@ -948,27 +1027,24 @@ files = [ [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.1" description = "Node.js virtual environment builder" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -993,28 +1069,29 @@ totp = ["cryptography"] [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1023,13 +1100,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.6.2" +version = "3.7.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"}, - {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"}, + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, ] [package.dependencies] @@ -1063,207 +1140,208 @@ test = ["mock", "nose"] [[package]] name = "psycopg" -version = "3.1.18" +version = "3.2.1" description = "PostgreSQL database adapter for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "psycopg-3.1.18-py3-none-any.whl", hash = "sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e"}, - {file = "psycopg-3.1.18.tar.gz", hash = "sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b"}, + {file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"}, + {file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"}, ] [package.dependencies] -psycopg-binary = {version = "3.1.18", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} -typing-extensions = ">=4.1" +psycopg-binary = {version = "3.2.1", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} +typing-extensions = ">=4.4" tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -binary = ["psycopg-binary (==3.1.18)"] -c = ["psycopg-c (==3.1.18)"] -dev = ["black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.4.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] +binary = ["psycopg-binary (==3.2.1)"] +c = ["psycopg-c (==3.2.1)"] +dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.6)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] pool = ["psycopg-pool"] -test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] +test = ["anyio (>=4.0)", "mypy (>=1.6)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] [[package]] name = "psycopg-binary" -version = "3.1.18" +version = "3.2.1" description = "PostgreSQL database adapter for Python -- C optimisation distribution" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "psycopg_binary-3.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c323103dfa663b88204cf5f028e83c77d7a715f9b6f51d2bbc8184b99ddd90a"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:887f8d856c91510148be942c7acd702ccf761a05f59f8abc123c22ab77b5a16c"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d322ba72cde4ca2eefc2196dad9ad7e52451acd2f04e3688d590290625d0c970"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:489aa4fe5a0b653b68341e9e44af247dedbbc655326854aa34c163ef1bcb3143"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ff0948457bfa8c0d35c46e3a75193906d1c275538877ba65907fd67aa059ad"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b15e3653c82384b043d820fc637199b5c6a36b37fa4a4943e0652785bb2bad5d"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f8ff3bc08b43f36fdc24fedb86d42749298a458c4724fb588c4d76823ac39f54"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1729d0e3dfe2546d823841eb7a3d003144189d6f5e138ee63e5227f8b75276a5"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:13bcd3742112446037d15e360b27a03af4b5afcf767f5ee374ef8f5dd7571b31"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:320047e3d3554b857e16c2b6b615a85e0db6a02426f4d203a4594a2f125dfe57"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:888a72c2aca4316ca6d4a619291b805677bae99bba2f6e31a3c18424a48c7e4d"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e4de16a637ec190cbee82e0c2dc4860fed17a23a35f7a1e6dc479a5c6876722"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6432047b8b24ef97e3fbee1d1593a0faaa9544c7a41a2c67d1f10e7621374c83"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d684227ef8212e27da5f2aff9d4d303cc30b27ac1702d4f6881935549486dd5"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67284e2e450dc7a9e4d76e78c0bd357dc946334a3d410defaeb2635607f632cd"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c9b6bd7fb5c6638cb32469674707649b526acfe786ba6d5a78ca4293d87bae4"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7121acc783c4e86d2d320a7fb803460fab158a7f0a04c5e8c5d49065118c1e73"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e28ff8f3de7b56588c2a398dc135fd9f157d12c612bd3daa7e6ba9872337f6f5"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c84a0174109f329eeda169004c7b7ca2e884a6305acab4a39600be67f915ed38"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:531381f6647fc267383dca88dbe8a70d0feff433a8e3d0c4939201fea7ae1b82"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b293e01057e63c3ac0002aa132a1071ce0fdb13b9ee2b6b45d3abdb3525c597d"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-win_amd64.whl", hash = "sha256:780a90bcb69bf27a8b08bc35b958e974cb6ea7a04cdec69e737f66378a344d68"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87dd9154b757a5fbf6d590f6f6ea75f4ad7b764a813ae04b1d91a70713f414a1"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f876ebbf92db70125f6375f91ab4bc6b27648aa68f90d661b1fc5affb4c9731c"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d2f0cb45e4574f8b2fe7c6d0a0e2eb58903a4fd1fbaf60954fba82d595ab7"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd27f713f2e5ef3fd6796e66c1a5203a27a30ecb847be27a78e1df8a9a5ae68c"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c38a4796abf7380f83b1653c2711cb2449dd0b2e5aca1caa75447d6fa5179c69"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2f7f95746efd1be2dc240248cc157f4315db3fd09fef2adfcc2a76e24aa5741"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4085f56a8d4fc8b455e8f44380705c7795be5317419aa5f8214f315e4205d804"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2e2484ae835dedc80cdc7f1b1a939377dc967fed862262cfd097aa9f50cade46"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3c2b039ae0c45eee4cd85300ef802c0f97d0afc78350946a5d0ec77dd2d7e834"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f54978c4b646dec77fefd8485fa82ec1a87807f334004372af1aaa6de9539a5"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-win_amd64.whl", hash = "sha256:9ffcbbd389e486d3fd83d30107bbf8b27845a295051ccabde240f235d04ed921"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c76659ae29a84f2c14f56aad305dd00eb685bd88f8c0a3281a9a4bc6bd7d2aa7"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7afcd6f1d55992f26d9ff7b0bd4ee6b475eb43aa3f054d67d32e09f18b0065"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:639dd78ac09b144b0119076783cb64e1128cc8612243e9701d1503c816750b2e"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1cf59e0bb12e031a48bb628aae32df3d0c98fd6c759cb89f464b1047f0ca9c8"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e262398e5d51563093edf30612cd1e20fedd932ad0994697d7781ca4880cdc3d"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:59701118c7d8842e451f1e562d08e8708b3f5d14974eefbce9374badd723c4ae"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dea4a59da7850192fdead9da888e6b96166e90608cf39e17b503f45826b16f84"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4575da95fc441244a0e2ebaf33a2b2f74164603341d2046b5cde0a9aa86aa7e2"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:812726266ab96de681f2c7dbd6b734d327f493a78357fcc16b2ac86ff4f4e080"}, - {file = "psycopg_binary-3.1.18-cp37-cp37m-win_amd64.whl", hash = "sha256:3e7ce4d988112ca6c75765c7f24c83bdc476a6a5ce00878df6c140ca32c3e16d"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:02bd4da45d5ee9941432e2e9bf36fa71a3ac21c6536fe7366d1bd3dd70d6b1e7"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39242546383f6b97032de7af30edb483d237a0616f6050512eee7b218a2aa8ee"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d46ae44d66bf6058a812467f6ae84e4e157dee281bfb1cfaeca07dee07452e85"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad35ac7fd989184bf4d38a87decfb5a262b419e8ba8dcaeec97848817412c64a"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:247474af262bdd5559ee6e669926c4f23e9cf53dae2d34c4d991723c72196404"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ebecbf2406cd6875bdd2453e31067d1bd8efe96705a9489ef37e93b50dc6f09"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1859aeb2133f5ecdd9cbcee155f5e38699afc06a365f903b1512c765fd8d457e"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:da917f6df8c6b2002043193cb0d74cc173b3af7eb5800ad69c4e1fbac2a71c30"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9e24e7b6a68a51cc3b162d0339ae4e1263b253e887987d5c759652f5692b5efe"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e252d66276c992319ed6cd69a3ffa17538943954075051e992143ccbf6dc3d3e"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:5d6e860edf877d4413e4a807e837d55e3a7c7df701e9d6943c06e460fa6c058f"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eea5f14933177ffe5c40b200f04f814258cc14b14a71024ad109f308e8bad414"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:824a1bfd0db96cc6bef2d1e52d9e0963f5bf653dd5bc3ab519a38f5e6f21c299"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87e9eeb80ce8ec8c2783f29bce9a50bbcd2e2342a340f159c3326bf4697afa1"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91074f78a9f890af5f2c786691575b6b93a4967ad6b8c5a90101f7b8c1a91d9c"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e05f6825f8db4428782135e6986fec79b139210398f3710ed4aa6ef41473c008"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f68ac2364a50d4cf9bb803b4341e83678668f1881a253e1224574921c69868c"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7ac1785d67241d5074f8086705fa68e046becea27964267ab3abd392481d7773"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:cd2a9f7f0d4dacc5b9ce7f0e767ae6cc64153264151f50698898c42cabffec0c"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3e4b0bb91da6f2238dbd4fbb4afc40dfb4f045bb611b92fce4d381b26413c686"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:74e498586b72fb819ca8ea82107747d0cb6e00ae685ea6d1ab3f929318a8ce2d"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:d4422af5232699f14b7266a754da49dc9bcd45eba244cf3812307934cd5d6679"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:cad2de17804c4cfee8640ae2b279d616bb9e4734ac3c17c13db5e40982bd710d"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:592b27d6c46a40f9eeaaeea7c1fef6f3c60b02c634365eb649b2d880669f149f"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a997efbaadb5e1a294fb5760e2f5643d7b8e4e3fe6cb6f09e6d605fd28e0291"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1d2b6438fb83376f43ebb798bf0ad5e57bc56c03c9c29c85bc15405c8c0ac5a"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1f087bd84bdcac78bf9f024ebdbfacd07fc0a23ec8191448a50679e2ac4a19e"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:415c3b72ea32119163255c6504085f374e47ae7345f14bc3f0ef1f6e0976a879"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f092114f10f81fb6bae544a0ec027eb720e2d9c74a4fcdaa9dd3899873136935"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06a7aae34edfe179ddc04da005e083ff6c6b0020000399a2cbf0a7121a8a22ea"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b018631e5c80ce9bc210b71ea885932f9cca6db131e4df505653d7e3873a938"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8a509aeaac364fa965454e80cd110fe6d48ba2c80f56c9b8563423f0b5c3cfd"}, + {file = "psycopg_binary-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:413977d18412ff83486eeb5875eb00b185a9391c57febac45b8993bf9c0ff489"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:62b1b7b07e00ee490afb39c0a47d8282a9c2822c7cfed9553a04b0058adf7e7f"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8afb07114ea9b924a4a0305ceb15354ccf0ef3c0e14d54b8dbeb03e50182dd7"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40bb515d042f6a345714ec0403df68ccf13f73b05e567837d80c886c7c9d3805"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6418712ba63cebb0c88c050b3997185b0ef54173b36568522d5634ac06153040"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:101472468d59c74bb8565fab603e032803fd533d16be4b2d13da1bab8deb32a3"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa3931f308ab4a479d0ee22dc04bea867a6365cac0172e5ddcba359da043854b"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc314a47d44fe1a8069b075a64abffad347a3a1d8652fed1bab5d3baea37acb2"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc304a46be1e291031148d9d95c12451ffe783ff0cc72f18e2cc7ec43cdb8c68"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f9e13600647087df5928875559f0eb8f496f53e6278b7da9511b4b3d0aff960"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b140182830c76c74d17eba27df3755a46442ce8d4fb299e7f1cf2f74a87c877b"}, + {file = "psycopg_binary-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:3c838806eeb99af39f934b7999e35f947a8e577997cc892c12b5053a97a9057f"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7066d3dca196ed0dc6172f9777b2d62e4f138705886be656cccff2d555234d60"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:28ada5f610468c57d8a4a055a8ea915d0085a43d794266c4f3b9d02f4288f4db"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e8213bf50af073b1aa8dc3cff123bfeedac86332a16c1b7274910bc88a847c7"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74d623261655a169bc84a9669890975c229f2fa6e19a7f2d10a77675dcf1a707"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42781ba94e8842ee98bca5a7d0c44cc9d067500fedca2d6a90fa3609b6d16b42"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e6669091d09f8ba36e10ce678a6d9916e110446236a9b92346464a3565635e"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b09e8a576a2ac69d695032ee76f31e03b30781828b5dd6d18c6a009e5a3d1c35"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8f28ff0cb9f1defdc4a6f8c958bf6787274247e7dfeca811f6e2f56602695fb1"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4c84fcac8a3a3479ac14673095cc4e1fdba2935499f72c436785ac679bec0d1a"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:950fd666ec9e9fe6a8eeb2b5a8f17301790e518953730ad44d715b59ffdbc67f"}, + {file = "psycopg_binary-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:334046a937bb086c36e2c6889fe327f9f29bfc085d678f70fac0b0618949f674"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:1d6833f607f3fc7b22226a9e121235d3b84c0eda1d3caab174673ef698f63788"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d353e028b8f848b9784450fc2abf149d53a738d451eab3ee4c85703438128b9"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34e369891f77d0738e5d25727c307d06d5344948771e5379ea29c76c6d84555"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ab58213cc976a1666f66bc1cb2e602315cd753b7981a8e17237ac2a185bd4a1"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0104a72a17aa84b3b7dcab6c84826c595355bf54bb6ea6d284dcb06d99c6801"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:059cbd4e6da2337e17707178fe49464ed01de867dc86c677b30751755ec1dc51"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:73f9c9b984be9c322b5ec1515b12df1ee5896029f5e72d46160eb6517438659c"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:af0469c00f24c4bec18c3d2ede124bf62688d88d1b8a5f3c3edc2f61046fe0d7"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:463d55345f73ff391df8177a185ad57b552915ad33f5cc2b31b930500c068b22"}, + {file = "psycopg_binary-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:302b86f92c0d76e99fe1b5c22c492ae519ce8b98b88d37ef74fda4c9e24c6b46"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0879b5d76b7d48678d31278242aaf951bc2d69ca4e4d7cef117e4bbf7bfefda9"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99e59f8a5f4dcd9cbdec445f3d8ac950a492fc0e211032384d6992ed3c17eb7"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84837e99353d16c6980603b362d0f03302d4b06c71672a6651f38df8a482923d"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ce965caf618061817f66c0906f0452aef966c293ae0933d4fa5a16ea6eaf5bb"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78c2007caf3c90f08685c5378e3ceb142bafd5636be7495f7d86ec8a977eaeef"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7a84b5eb194a258116154b2a4ff2962ea60ea52de089508db23a51d3d6b1c7d1"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4a42b8f9ab39affcd5249b45cac763ac3cf12df962b67e23fd15a2ee2932afe5"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:788ffc43d7517c13e624c83e0e553b7b8823c9655e18296566d36a829bfb373f"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:21927f41c4d722ae8eb30d62a6ce732c398eac230509af5ba1749a337f8a63e2"}, + {file = "psycopg_binary-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:921f0c7f39590763d64a619de84d1b142587acc70fd11cbb5ba8fa39786f3073"}, ] [[package]] name = "pydantic" -version = "2.6.4" +version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, - {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.3" -typing-extensions = ">=4.6.1" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.16.3" -description = "" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, - {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, - {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, - {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, - {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, - {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, - {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, - {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, - {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, - {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, - {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, - {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, - {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, - {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, - {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, - {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, - {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, - {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, - {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, - {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, - {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, - {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, - {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, - {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, - {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, ] [package.dependencies] @@ -1271,17 +1349,17 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.2.1" +version = "2.3.4" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, - {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, + {file = "pydantic_settings-2.3.4-py3-none-any.whl", hash = "sha256:11ad8bacb68a045f00e4f862c7a718c8a9ec766aa8fd4c32e39a0594b207b53a"}, + {file = "pydantic_settings-2.3.4.tar.gz", hash = "sha256:c5802e3d62b78e82522319bbc9b8f8ffb28ad1c988a99311d04f2a6051fca0a7"}, ] [package.dependencies] -pydantic = ">=2.3.0" +pydantic = ">=2.7.0" python-dotenv = ">=0.21.0" [package.extras] @@ -1478,13 +1556,13 @@ files = [ [[package]] name = "sentry-sdk" -version = "1.41.0" +version = "1.45.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.41.0.tar.gz", hash = "sha256:4f2d6c43c07925d8cd10dfbd0970ea7cb784f70e79523cca9dbcd72df38e5a46"}, - {file = "sentry_sdk-1.41.0-py2.py3-none-any.whl", hash = "sha256:be4f8f4b29a80b6a3b71f0f31487beb9e296391da20af8504498a328befed53f"}, + {file = "sentry-sdk-1.45.0.tar.gz", hash = "sha256:509aa9678c0512344ca886281766c2e538682f8acfa50fd8d405f8c417ad0625"}, + {file = "sentry_sdk-1.45.0-py2.py3-none-any.whl", hash = "sha256:1ce29e30240cc289a027011103a8c83885b15ef2f316a60bcc7c5300afa144f1"}, ] [package.dependencies] @@ -1499,6 +1577,7 @@ asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] @@ -1509,6 +1588,7 @@ grpcio = ["grpcio (>=1.21.1)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] pure-eval = ["asttokens", "executing", "pure-eval"] @@ -1561,64 +1641,64 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.28" +version = "2.0.31" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, - {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, - {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, + {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, + {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] @@ -1648,13 +1728,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlmodel" -version = "0.0.19" +version = "0.0.20" description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." optional = false python-versions = ">=3.7" files = [ - {file = "sqlmodel-0.0.19-py3-none-any.whl", hash = "sha256:6c8125d4101970d031e9aae970b20cbeaf44149989f8366d939f4ab21aab8763"}, - {file = "sqlmodel-0.0.19.tar.gz", hash = "sha256:95449b0b48a40a3eecf0a629fa5735b9dfc8a5574a91090d24ca17f02246ad96"}, + {file = "sqlmodel-0.0.20-py3-none-any.whl", hash = "sha256:744756c49e24095808984754cc4d3a32c2d8361fef803c4914fadcb912239bc9"}, + {file = "sqlmodel-0.0.20.tar.gz", hash = "sha256:94dd1f63e4ceb0ab405e304e1ad3e8b8c8800b47c3ca5f68736807be8e5b9314"}, ] [package.dependencies] @@ -1680,17 +1760,18 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "tenacity" -version = "8.2.3" +version = "8.5.0" description = "Retry code until it succeeds" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, - {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, + {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, + {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, ] [package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "tomli" @@ -1705,24 +1786,24 @@ files = [ [[package]] name = "types-passlib" -version = "1.7.7.20240311" +version = "1.7.7.20240327" description = "Typing stubs for passlib" optional = false python-versions = ">=3.8" files = [ - {file = "types-passlib-1.7.7.20240311.tar.gz", hash = "sha256:287dd27cec5421daf6be5c295f681baf343c146038c8bde4db783bcac1beccb7"}, - {file = "types_passlib-1.7.7.20240311-py3-none-any.whl", hash = "sha256:cd44166e9347ae516f4830046cd1673c1ef90a5cc7ddd1356cf8a14892f29249"}, + {file = "types-passlib-1.7.7.20240327.tar.gz", hash = "sha256:4cce6a1a3a6afee9fc4728b4d9784300764ac2be747f5bcc01646d904b85f4bb"}, + {file = "types_passlib-1.7.7.20240327-py3-none-any.whl", hash = "sha256:3a3b7f4258b71034d2e2f4f307d6810f9904f906cdf375514c8bdbdb28a4ad23"}, ] [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -1825,13 +1906,13 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)" [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, ] [package.dependencies] @@ -1840,91 +1921,91 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "watchfiles" -version = "0.21.0" +version = "0.22.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.8" files = [ - {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"}, - {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c"}, - {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9"}, - {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9"}, - {file = "watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293"}, - {file = "watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"}, - {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"}, - {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"}, - {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"}, - {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"}, - {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c"}, - {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235"}, - {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7"}, - {file = "watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3"}, - {file = "watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094"}, - {file = "watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6"}, - {file = "watchfiles-0.21.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99"}, - {file = "watchfiles-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562"}, - {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19"}, - {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0"}, - {file = "watchfiles-0.21.0-cp38-none-win32.whl", hash = "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214"}, - {file = "watchfiles-0.21.0-cp38-none-win_amd64.whl", hash = "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca"}, - {file = "watchfiles-0.21.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e"}, - {file = "watchfiles-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28"}, - {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6"}, - {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49"}, - {file = "watchfiles-0.21.0-cp39-none-win32.whl", hash = "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94"}, - {file = "watchfiles-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097"}, - {file = "watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3"}, + {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"}, + {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"}, + {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"}, + {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"}, + {file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"}, + {file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"}, + {file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"}, + {file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"}, + {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"}, + {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"}, + {file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"}, + {file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"}, + {file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"}, + {file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"}, + {file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"}, + {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"}, + {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"}, + {file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"}, + {file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"}, + {file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"}, + {file = "watchfiles-0.22.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d47e9ef1a94cc7a536039e46738e17cce058ac1593b2eccdede8bf72e45f372a"}, + {file = "watchfiles-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28f393c1194b6eaadcdd8f941307fc9bbd7eb567995232c830f6aef38e8a6e88"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd64f3a4db121bc161644c9e10a9acdb836853155a108c2446db2f5ae1778c3d"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2abeb79209630da981f8ebca30a2c84b4c3516a214451bfc5f106723c5f45843"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cc382083afba7918e32d5ef12321421ef43d685b9a67cc452a6e6e18920890e"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d048ad5d25b363ba1d19f92dcf29023988524bee6f9d952130b316c5802069cb"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:103622865599f8082f03af4214eaff90e2426edff5e8522c8f9e93dc17caee13"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e1f3cf81f1f823e7874ae563457828e940d75573c8fbf0ee66818c8b6a9099"}, + {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8597b6f9dc410bdafc8bb362dac1cbc9b4684a8310e16b1ff5eee8725d13dcd6"}, + {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b04a2cbc30e110303baa6d3ddce8ca3664bc3403be0f0ad513d1843a41c97d1"}, + {file = "watchfiles-0.22.0-cp38-none-win32.whl", hash = "sha256:b610fb5e27825b570554d01cec427b6620ce9bd21ff8ab775fc3a32f28bba63e"}, + {file = "watchfiles-0.22.0-cp38-none-win_amd64.whl", hash = "sha256:fe82d13461418ca5e5a808a9e40f79c1879351fcaeddbede094028e74d836e86"}, + {file = "watchfiles-0.22.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3973145235a38f73c61474d56ad6199124e7488822f3a4fc97c72009751ae3b0"}, + {file = "watchfiles-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:280a4afbc607cdfc9571b9904b03a478fc9f08bbeec382d648181c695648202f"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d883351a34c01bd53cfa75cd0292e3f7e268bacf2f9e33af4ecede7e21d1d"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9165bcab15f2b6d90eedc5c20a7f8a03156b3773e5fb06a790b54ccecdb73385"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc1b9b56f051209be458b87edb6856a449ad3f803315d87b2da4c93b43a6fe72"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc1fc25a1dedf2dd952909c8e5cb210791e5f2d9bc5e0e8ebc28dd42fed7562"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92d2d2706d2b862ce0568b24987eba51e17e14b79a1abcd2edc39e48e743c8"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b94e14b88409c58cdf4a8eaf0e67dfd3ece7e9ce7140ea6ff48b0407a593ec"}, + {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96eec15e5ea7c0b6eb5bfffe990fc7c6bd833acf7e26704eb18387fb2f5fd087"}, + {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:28324d6b28bcb8d7c1041648d7b63be07a16db5510bea923fc80b91a2a6cbed6"}, + {file = "watchfiles-0.22.0-cp39-none-win32.whl", hash = "sha256:8c3e3675e6e39dc59b8fe5c914a19d30029e36e9f99468dddffd432d8a7b1c93"}, + {file = "watchfiles-0.22.0-cp39-none-win_amd64.whl", hash = "sha256:25c817ff2a86bc3de3ed2df1703e3d24ce03479b27bb4527c57e722f8554d971"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"}, + {file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"}, ] [package.dependencies] @@ -2014,4 +2095,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "1ce11888d9f96ef6ac10ec5f4409e7ffab06ba648aa720d75079924dbfbb6de5" +content-hash = "bf09330cd76ebeb0e198c25ab36664130490e052d66aedc20e7de7ba240681d5" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index e73475d68c..06076c5637 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -20,7 +20,7 @@ jinja2 = "^3.1.4" alembic = "^1.12.1" httpx = "^0.25.1" psycopg = {extras = ["binary"], version = "^3.1.13"} -sqlmodel = "^0.0.19" +sqlmodel = "^0.0.20" # Pin bcrypt until passlib supports the latest bcrypt = "4.0.1" pydantic-settings = "^2.2.1" From 8ffb2491dd739ea38d9d5d149ff90e2a375f637d Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jul 2024 22:49:22 +0000 Subject: [PATCH 566/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 398badd114..66ac5aab3b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -21,6 +21,7 @@ ### Refactors +* ♻️ Edit refactor db models to use UUID's instead of integer ID's. PR [#1259](https://github.com/tiangolo/full-stack-fastapi-template/pull/1259) by [@estebanx64](https://github.com/estebanx64). * ♻️ Update form inputs width. PR [#1263](https://github.com/tiangolo/full-stack-fastapi-template/pull/1263) by [@alejsdev](https://github.com/alejsdev). * ♻️ Replace deprecated utcnow() with now(timezone.utc) in utils module. PR [#1247](https://github.com/tiangolo/full-stack-fastapi-template/pull/1247) by [@jalvarezz13](https://github.com/jalvarezz13). * 🎨 Format frontend. PR [#1262](https://github.com/tiangolo/full-stack-fastapi-template/pull/1262) by [@alejsdev](https://github.com/alejsdev). From 11b3d9d89d9ca8d67e518b171d38214f810ca8e4 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:00:02 -0500 Subject: [PATCH 567/771] =?UTF-8?q?=E2=9C=A8=20Add=20initial=20setup=20for?= =?UTF-8?q?=20frontend=20/=20end-to-end=20tests=20with=20Playwright=20(#12?= =?UTF-8?q?61)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .github/workflows/playwright.yml | 66 ++++++++++++++++++++++ .gitignore | 5 ++ frontend/.gitignore | 4 ++ frontend/biome.json | 8 ++- frontend/package-lock.json | 96 +++++++++++++++++++++++++++++++- frontend/package.json | 3 +- frontend/playwright.config.ts | 91 ++++++++++++++++++++++++++++++ frontend/tests/auth.setup.ts | 12 ++++ frontend/tests/example.spec.ts | 8 +++ 9 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 frontend/playwright.config.ts create mode 100644 frontend/tests/auth.setup.ts create mode 100644 frontend/tests/example.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000000..d242af4f18 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,66 @@ +name: Playwright Tests + +on: + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + workflow_dispatch: + inputs: + debug_enabled: + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + required: false + default: 'false' + +jobs: + + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} + with: + limit-access-to-actor: true + - name: Install dependencies + run: npm ci + working-directory: frontend + - name: Install Playwright Browsers + run: npx playwright install --with-deps + working-directory: frontend + - run: docker compose build + - run: docker compose down -v --remove-orphans + - run: docker compose up -d + - name: Run Playwright tests + run: npx playwright test + working-directory: frontend + - run: docker compose down -v --remove-orphans + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: frontend/playwright-report/ + retention-days: 30 + + # https://github.com/marketplace/actions/alls-green#why + e2e-alls-green: # This job does nothing and is only used for the branch protection + if: always() + needs: + - test + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.gitignore b/.gitignore index 722d5e71d9..a6dd346572 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ .vscode +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/frontend/.gitignore b/frontend/.gitignore index 4eb6586f19..dfc4015cce 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -23,3 +23,7 @@ openapi.json *.njsproj *.sln *.sw? +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/frontend/biome.json b/frontend/biome.json index b6e397eb49..14597ce328 100644 --- a/frontend/biome.json +++ b/frontend/biome.json @@ -4,7 +4,13 @@ "enabled": true }, "files": { - "ignore": ["node_modules", "src/client/", "src/routeTree.gen.ts"] + "ignore": [ + "node_modules", + "src/client/", + "src/routeTree.gen.ts", + "playwright.config.ts", + "playwright-report" + ] }, "linter": { "enabled": true, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7c1556613f..a17e86cea5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,9 +27,10 @@ "devDependencies": { "@biomejs/biome": "1.6.1", "@hey-api/openapi-ts": "^0.34.1", + "@playwright/test": "^1.45.2", "@tanstack/router-devtools": "1.19.1", "@tanstack/router-vite-plugin": "1.19.0", - "@types/node": "20.10.5", + "@types/node": "^20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@vitejs/plugin-react-swc": "^3.5.0", @@ -2123,6 +2124,21 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, + "node_modules/@playwright/test": { + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.2.tgz", + "integrity": "sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==", + "dev": true, + "dependencies": { + "playwright": "1.45.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -3318,6 +3334,50 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "node_modules/playwright": { + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz", + "integrity": "sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==", + "dev": true, + "dependencies": { + "playwright-core": "1.45.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.2.tgz", + "integrity": "sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", @@ -5273,6 +5333,15 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, + "@playwright/test": { + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.2.tgz", + "integrity": "sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==", + "dev": true, + "requires": { + "playwright": "1.45.2" + } + }, "@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -6063,6 +6132,31 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "playwright": { + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz", + "integrity": "sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.45.2" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, + "playwright-core": { + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.2.tgz", + "integrity": "sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==", + "dev": true + }, "postcss": { "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0aff516f4c..b429c4d4c0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,9 +30,10 @@ "devDependencies": { "@biomejs/biome": "1.6.1", "@hey-api/openapi-ts": "^0.34.1", + "@playwright/test": "^1.45.2", "@tanstack/router-devtools": "1.19.1", "@tanstack/router-vite-plugin": "1.19.0", - "@types/node": "20.10.5", + "@types/node": "^20.10.5", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@vitejs/plugin-react-swc": "^3.5.0", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000000..4208a946bb --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,91 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:5173', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { name: 'setup', testMatch: /.*\.setup\.ts/ }, + + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/user.json', + }, + dependencies: ['setup'], + }, + + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // storageState: 'playwright/.auth/user.json', + // }, + // dependencies: ['setup'], + // }, + + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // storageState: 'playwright/.auth/user.json', + // }, + // dependencies: ['setup'], + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/frontend/tests/auth.setup.ts b/frontend/tests/auth.setup.ts new file mode 100644 index 0000000000..48f1acb07e --- /dev/null +++ b/frontend/tests/auth.setup.ts @@ -0,0 +1,12 @@ +import { test as setup } from "@playwright/test" + +const authFile = "playwright/.auth/user.json" + +setup("authenticate", async ({ page }) => { + await page.goto("/login") + await page.getByPlaceholder("Email").fill("admin@example.com") + await page.getByPlaceholder("Password").fill("changethis") + await page.getByRole("button", { name: "Log In" }).click() + await page.waitForURL("/") + await page.context().storageState({ path: authFile }) +}) diff --git a/frontend/tests/example.spec.ts b/frontend/tests/example.spec.ts new file mode 100644 index 0000000000..f5dc2e73ca --- /dev/null +++ b/frontend/tests/example.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); From e3e4e049d70cd8bac80895577bb7f88d3c1d9789 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jul 2024 23:00:20 +0000 Subject: [PATCH 568/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 66ac5aab3b..31c4e5c3bd 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Add initial setup for frontend / end-to-end tests with Playwright. PR [#1261](https://github.com/tiangolo/full-stack-fastapi-template/pull/1261) by [@alejsdev](https://github.com/alejsdev). * ✨ Add mailcatcher configuration. PR [#1244](https://github.com/tiangolo/full-stack-fastapi-template/pull/1244) by [@patrick91](https://github.com/patrick91). * ✨ Introduce pagination in items. PR [#1239](https://github.com/tiangolo/full-stack-fastapi-template/pull/1239) by [@patrick91](https://github.com/patrick91). * 🗃️ Add max_length validation for database models and input data. PR [#1233](https://github.com/tiangolo/full-stack-fastapi-template/pull/1233) by [@estebanx64](https://github.com/estebanx64). From 97ce66e2176ac0a9f84382eca041b7311b36ebfe Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:04:29 -0500 Subject: [PATCH 569/771] =?UTF-8?q?=E2=9C=A8=20Add=20Login=20e2e=20tests?= =?UTF-8?q?=20(#1264)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- frontend/src/components/Common/UserMenu.tsx | 1 + frontend/tests/example.spec.ts | 8 -- frontend/tests/login.spec.ts | 114 ++++++++++++++++++++ 3 files changed, 115 insertions(+), 8 deletions(-) delete mode 100644 frontend/tests/example.spec.ts create mode 100644 frontend/tests/login.spec.ts diff --git a/frontend/src/components/Common/UserMenu.tsx b/frontend/src/components/Common/UserMenu.tsx index 324dd92f9f..e3d54ac26b 100644 --- a/frontend/src/components/Common/UserMenu.tsx +++ b/frontend/src/components/Common/UserMenu.tsx @@ -35,6 +35,7 @@ const UserMenu = () => { icon={} bg="ui.main" isRound + data-testid="user-menu" /> } as={Link} to="settings"> diff --git a/frontend/tests/example.spec.ts b/frontend/tests/example.spec.ts deleted file mode 100644 index f5dc2e73ca..0000000000 --- a/frontend/tests/example.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('has title', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); -}); diff --git a/frontend/tests/login.spec.ts b/frontend/tests/login.spec.ts new file mode 100644 index 0000000000..eb7d5e4128 --- /dev/null +++ b/frontend/tests/login.spec.ts @@ -0,0 +1,114 @@ +import { type Page, expect, test } from "@playwright/test" + +test.use({ storageState: { cookies: [], origins: [] } }) + +type OptionsType = { + exact?: boolean +} + +const fillForm = async (page: Page, email: string, password: string) => { + await page.getByPlaceholder("Email").fill(email) + await page.getByPlaceholder("Password", { exact: true }).fill(password) +} + +const verifyInput = async ( + page: Page, + placeholder: string, + options?: OptionsType, +) => { + const input = page.getByPlaceholder(placeholder, options) + await expect(input).toBeVisible() + await expect(input).toHaveText("") + await expect(input).toBeEditable() +} + +test("Inputs are visible, empty and editable", async ({ page }) => { + await page.goto("/login") + + await verifyInput(page, "Email") + await verifyInput(page, "Password", { exact: true }) +}) + +test("Log In button is visible", async ({ page }) => { + await page.goto("/login") + + await expect(page.getByRole("button", { name: "Log In" })).toBeVisible() +}) + +test("Forgot Password link is visible", async ({ page }) => { + await page.goto("/login") + + await expect( + page.getByRole("link", { name: "Forgot password?" }), + ).toBeVisible() +}) + +test("Log in with valid email and password ", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, "admin@example.com", "changethis") + await page.getByRole("button", { name: "Log In" }).click() + + await page.waitForURL("/") + + await expect( + page.getByText("Welcome back, nice to see you again!"), + ).toBeVisible() +}) + +test("Log in with invalid email", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, "invalidemail", "changethis") + await page.getByRole("button", { name: "Log In" }).click() + + await expect(page.getByText("Invalid email address")).toBeVisible() +}) + +test("Log in with invalid password", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, "admin@example.com", "changethat") + await page.getByRole("button", { name: "Log In" }).click() + + await expect(page.getByText("Incorrect email or password")).toBeVisible() +}) + +// Log out + +test("Successful log out", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, "admin@example.com", "changethis") + await page.getByRole("button", { name: "Log In" }).click() + + await page.waitForURL("/") + + await expect( + page.getByText("Welcome back, nice to see you again!"), + ).toBeVisible() + + await page.getByTestId("user-menu").click() + await page.getByRole("menuitem", { name: "Log out" }).click() + await page.waitForURL("/login") +}) + +test("Logged-out user cannot access protected routes", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, "admin@example.com", "changethis") + await page.getByRole("button", { name: "Log In" }).click() + + await page.waitForURL("/") + + await expect( + page.getByText("Welcome back, nice to see you again!"), + ).toBeVisible() + + await page.getByTestId("user-menu").click() + await page.getByRole("menuitem", { name: "Log out" }).click() + await page.waitForURL("/login") + + await page.goto("/settings") + await page.waitForURL("/login") +}) From d6dbcafcda1925adf4af7e5dd29c493defd6556e Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 22 Jul 2024 23:04:45 +0000 Subject: [PATCH 570/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 31c4e5c3bd..25c4f99b4c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Add Login e2e tests. PR [#1264](https://github.com/tiangolo/full-stack-fastapi-template/pull/1264) by [@alejsdev](https://github.com/alejsdev). * ✨ Add initial setup for frontend / end-to-end tests with Playwright. PR [#1261](https://github.com/tiangolo/full-stack-fastapi-template/pull/1261) by [@alejsdev](https://github.com/alejsdev). * ✨ Add mailcatcher configuration. PR [#1244](https://github.com/tiangolo/full-stack-fastapi-template/pull/1244) by [@patrick91](https://github.com/patrick91). * ✨ Introduce pagination in items. PR [#1239](https://github.com/tiangolo/full-stack-fastapi-template/pull/1239) by [@patrick91](https://github.com/patrick91). From b45dfe91aa09776f252298f756685abe69b75612 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:40:56 -0500 Subject: [PATCH 571/771] =?UTF-8?q?=E2=9C=A8=20Add=20Sign=20Up=20and=20mak?= =?UTF-8?q?e=20`OPEN=5FUSER=5FREGISTRATION=3DTrue`=20by=20default=20(#1265?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useAuth.ts | 29 +++++- frontend/src/routeTree.gen.ts | 11 +++ frontend/src/routes/login.tsx | 16 ++-- frontend/src/routes/signup.tsx | 163 +++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 frontend/src/routes/signup.tsx diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 397e408f8d..6b2499fee8 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from "@tanstack/react-query" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useNavigate } from "@tanstack/react-router" import { useState } from "react" @@ -8,8 +8,10 @@ import { type ApiError, LoginService, type UserPublic, + type UserRegister, UsersService, } from "../client" +import useCustomToast from "./useCustomToast" const isLoggedIn = () => { return localStorage.getItem("access_token") !== null @@ -18,12 +20,36 @@ const isLoggedIn = () => { const useAuth = () => { const [error, setError] = useState(null) const navigate = useNavigate() + const showToast = useCustomToast() + const queryClient = useQueryClient() const { data: user, isLoading } = useQuery({ queryKey: ["currentUser"], queryFn: UsersService.readUserMe, enabled: isLoggedIn(), }) + const signUpMutation = useMutation({ + mutationFn: (data: UserRegister) => + UsersService.registerUser({ requestBody: data }), + + onSuccess: () => { + navigate({ to: "/login" }) + showToast("Success!", "User created successfully.", "success") + }, + onError: (err: ApiError) => { + let errDetail = (err.body as any)?.detail + + if (err instanceof AxiosError) { + errDetail = err.message + } + + showToast("Something went wrong.", `${errDetail}`, "error") + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["users"] }) + }, + }) + const login = async (data: AccessToken) => { const response = await LoginService.loginAccessToken({ formData: data, @@ -57,6 +83,7 @@ const useAuth = () => { } return { + signUpMutation, loginMutation, logout, user, diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 395866a44b..0e78c9ba20 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -11,6 +11,7 @@ // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as SignupImport } from './routes/signup' import { Route as ResetPasswordImport } from './routes/reset-password' import { Route as RecoverPasswordImport } from './routes/recover-password' import { Route as LoginImport } from './routes/login' @@ -22,6 +23,11 @@ import { Route as LayoutAdminImport } from './routes/_layout/admin' // Create/Update Routes +const SignupRoute = SignupImport.update({ + path: '/signup', + getParentRoute: () => rootRoute, +} as any) + const ResetPasswordRoute = ResetPasswordImport.update({ path: '/reset-password', getParentRoute: () => rootRoute, @@ -82,6 +88,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ResetPasswordImport parentRoute: typeof rootRoute } + '/signup': { + preLoaderRoute: typeof SignupImport + parentRoute: typeof rootRoute + } '/_layout/admin': { preLoaderRoute: typeof LayoutAdminImport parentRoute: typeof LayoutImport @@ -113,6 +123,7 @@ export const routeTree = rootRoute.addChildren([ LoginRoute, RecoverPasswordRoute, ResetPasswordRoute, + SignupRoute, ]) /* prettier-ignore-end */ diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 2c35208c71..20a9be6564 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -1,7 +1,6 @@ import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons" import { Button, - Center, Container, FormControl, FormErrorMessage, @@ -11,6 +10,7 @@ import { InputGroup, InputRightElement, Link, + Text, useBoolean, } from "@chakra-ui/react" import { @@ -126,14 +126,18 @@ function Login() { {error && {error}} -
- - Forgot password? - -
+ + Forgot password? + + + Don't have an account?{" "} + + Sign up + + ) diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx new file mode 100644 index 0000000000..1a27e3b870 --- /dev/null +++ b/frontend/src/routes/signup.tsx @@ -0,0 +1,163 @@ +import { + Button, + Container, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Image, + Input, + Link, + Text, +} from "@chakra-ui/react" +import { + Link as RouterLink, + createFileRoute, + redirect, +} from "@tanstack/react-router" +import { type SubmitHandler, useForm } from "react-hook-form" + +import Logo from "/assets/images/fastapi-logo.svg" +import type { UserRegister } from "../client" +import useAuth, { isLoggedIn } from "../hooks/useAuth" +import { confirmPasswordRules, emailPattern, passwordRules } from "../utils" + +export const Route = createFileRoute("/signup")({ + component: SignUp, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: "/", + }) + } + }, +}) + +interface UserRegisterForm extends UserRegister { + confirm_password: string +} + +function SignUp() { + const { signUpMutation } = useAuth() + const { + register, + handleSubmit, + getValues, + formState: { errors, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + email: "", + full_name: "", + password: "", + confirm_password: "", + }, + }) + + const onSubmit: SubmitHandler = (data) => { + signUpMutation.mutate(data) + } + + return ( + <> + + + + + + Full Name + + + {errors.full_name && ( + {errors.full_name.message} + )} + + + + Email + + + {errors.email && ( + {errors.email.message} + )} + + + + Password + + + {errors.password && ( + {errors.password.message} + )} + + + + Confirm Password + + + + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + Already have an account?{" "} + + Log In + + + + + + ) +} + +export default SignUp From 6d46acbaabfe1c52094b4faa3fad074879f16c24 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 23 Jul 2024 00:41:16 +0000 Subject: [PATCH 572/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 25c4f99b4c..6d9caa688b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Add Sign Up and make `OPEN_USER_REGISTRATION=True` by default. PR [#1265](https://github.com/tiangolo/full-stack-fastapi-template/pull/1265) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Login e2e tests. PR [#1264](https://github.com/tiangolo/full-stack-fastapi-template/pull/1264) by [@alejsdev](https://github.com/alejsdev). * ✨ Add initial setup for frontend / end-to-end tests with Playwright. PR [#1261](https://github.com/tiangolo/full-stack-fastapi-template/pull/1261) by [@alejsdev](https://github.com/alejsdev). * ✨ Add mailcatcher configuration. PR [#1244](https://github.com/tiangolo/full-stack-fastapi-template/pull/1244) by [@patrick91](https://github.com/patrick91). From 526ccb562053732178f073595cd40b887c40e5e2 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:59:39 -0500 Subject: [PATCH 573/771] =?UTF-8?q?=F0=9F=94=A7=20Update=20Playwright=20co?= =?UTF-8?q?nfig=20and=20tests=20to=20use=20env=20variables=20(#1266)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 19 +++++++++++++++++++ frontend/package.json | 1 + frontend/playwright.config.ts | 1 + frontend/tests/auth.setup.ts | 6 ++++-- frontend/tests/config.ts | 21 +++++++++++++++++++++ frontend/tests/login.spec.ts | 13 +++++++------ frontend/tsconfig.json | 2 +- 7 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 frontend/tests/config.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a17e86cea5..1a13684c74 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -34,6 +34,7 @@ "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@vitejs/plugin-react-swc": "^3.5.0", + "dotenv": "^16.4.5", "typescript": "^5.2.2", "vite": "^5.0.13" } @@ -2911,6 +2912,18 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5829,6 +5842,12 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, + "dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index b429c4d4c0..8533517148 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,6 +37,7 @@ "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@vitejs/plugin-react-swc": "^3.5.0", + "dotenv": "^16.4.5", "typescript": "^5.2.2", "vite": "^5.0.13" } diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index 4208a946bb..dcdd6fec81 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -1,5 +1,6 @@ import { defineConfig, devices } from '@playwright/test'; + /** * Read environment variables from file. * https://github.com/motdotla/dotenv diff --git a/frontend/tests/auth.setup.ts b/frontend/tests/auth.setup.ts index 48f1acb07e..d4e196e31e 100644 --- a/frontend/tests/auth.setup.ts +++ b/frontend/tests/auth.setup.ts @@ -1,11 +1,13 @@ import { test as setup } from "@playwright/test" +import { firstSuperuser, firstSuperuserPassword } from "./config.ts" const authFile = "playwright/.auth/user.json" + setup("authenticate", async ({ page }) => { await page.goto("/login") - await page.getByPlaceholder("Email").fill("admin@example.com") - await page.getByPlaceholder("Password").fill("changethis") + await page.getByPlaceholder("Email").fill(firstSuperuser) + await page.getByPlaceholder("Password").fill(firstSuperuserPassword) await page.getByRole("button", { name: "Log In" }).click() await page.waitForURL("/") await page.context().storageState({ path: authFile }) diff --git a/frontend/tests/config.ts b/frontend/tests/config.ts new file mode 100644 index 0000000000..d2265bbf8f --- /dev/null +++ b/frontend/tests/config.ts @@ -0,0 +1,21 @@ +import dotenv from 'dotenv'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +dotenv.config({ path: path.join(__dirname, '../../.env') }); + +const { FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD } = process.env; + +if (typeof FIRST_SUPERUSER !== "string") { + throw new Error("Environment variable FIRST_SUPERUSER is undefined"); +} + +if (typeof FIRST_SUPERUSER_PASSWORD !== "string") { + throw new Error("Environment variable FIRST_SUPERUSER_PASSWORD is undefined"); +} + +export const firstSuperuser = FIRST_SUPERUSER as string; +export const firstSuperuserPassword = FIRST_SUPERUSER_PASSWORD as string; diff --git a/frontend/tests/login.spec.ts b/frontend/tests/login.spec.ts index eb7d5e4128..ed1a8506dc 100644 --- a/frontend/tests/login.spec.ts +++ b/frontend/tests/login.spec.ts @@ -1,4 +1,5 @@ import { type Page, expect, test } from "@playwright/test" +import { firstSuperuser, firstSuperuserPassword } from "./config.ts" test.use({ storageState: { cookies: [], origins: [] } }) @@ -46,7 +47,7 @@ test("Forgot Password link is visible", async ({ page }) => { test("Log in with valid email and password ", async ({ page }) => { await page.goto("/login") - await fillForm(page, "admin@example.com", "changethis") + await fillForm(page, firstSuperuser, firstSuperuserPassword) await page.getByRole("button", { name: "Log In" }).click() await page.waitForURL("/") @@ -59,7 +60,7 @@ test("Log in with valid email and password ", async ({ page }) => { test("Log in with invalid email", async ({ page }) => { await page.goto("/login") - await fillForm(page, "invalidemail", "changethis") + await fillForm(page, "invalidemail", firstSuperuserPassword) await page.getByRole("button", { name: "Log In" }).click() await expect(page.getByText("Invalid email address")).toBeVisible() @@ -67,8 +68,8 @@ test("Log in with invalid email", async ({ page }) => { test("Log in with invalid password", async ({ page }) => { await page.goto("/login") - - await fillForm(page, "admin@example.com", "changethat") + // TODO: Add a random password utility + await fillForm(page, firstSuperuser, "changethat") await page.getByRole("button", { name: "Log In" }).click() await expect(page.getByText("Incorrect email or password")).toBeVisible() @@ -79,7 +80,7 @@ test("Log in with invalid password", async ({ page }) => { test("Successful log out", async ({ page }) => { await page.goto("/login") - await fillForm(page, "admin@example.com", "changethis") + await fillForm(page, firstSuperuser, firstSuperuserPassword) await page.getByRole("button", { name: "Log In" }).click() await page.waitForURL("/") @@ -96,7 +97,7 @@ test("Successful log out", async ({ page }) => { test("Logged-out user cannot access protected routes", async ({ page }) => { await page.goto("/login") - await fillForm(page, "admin@example.com", "changethis") + await fillForm(page, firstSuperuser, firstSuperuserPassword) await page.getByRole("button", { name: "Log In" }).click() await page.waitForURL("/") diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index a7fc6fbf23..baadbb9fb1 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -20,6 +20,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"], + "include": ["src", "*.ts", "**/*.ts"], "references": [{ "path": "./tsconfig.node.json" }] } From f70b811277823f1b1cbbd47645fcaab0f2e988f9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 23 Jul 2024 19:59:57 +0000 Subject: [PATCH 574/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 6d9caa688b..5e184b8d17 100644 --- a/release-notes.md +++ b/release-notes.md @@ -24,6 +24,7 @@ ### Refactors +* 🔧 Update Playwright config and tests to use env variables. PR [#1266](https://github.com/tiangolo/full-stack-fastapi-template/pull/1266) by [@alejsdev](https://github.com/alejsdev). * ♻️ Edit refactor db models to use UUID's instead of integer ID's. PR [#1259](https://github.com/tiangolo/full-stack-fastapi-template/pull/1259) by [@estebanx64](https://github.com/estebanx64). * ♻️ Update form inputs width. PR [#1263](https://github.com/tiangolo/full-stack-fastapi-template/pull/1263) by [@alejsdev](https://github.com/alejsdev). * ♻️ Replace deprecated utcnow() with now(timezone.utc) in utils module. PR [#1247](https://github.com/tiangolo/full-stack-fastapi-template/pull/1247) by [@jalvarezz13](https://github.com/jalvarezz13). From e56b3998cce498f1d44d1363550176d552e79455 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 29 Jul 2024 12:53:43 -0500 Subject: [PATCH 575/771] =?UTF-8?q?=E2=9C=A8=20Add=20Sign=20Up=20e2e=20tes?= =?UTF-8?q?ts=20(#1268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/routes/signup.tsx | 3 +- frontend/tests/sign-up.spec.ts | 169 +++++++++++++++++++++++++++++++++ frontend/tests/utils/random.ts | 11 +++ 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 frontend/tests/sign-up.spec.ts create mode 100644 frontend/tests/utils/random.ts diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index 1a27e3b870..b021e73698 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -87,7 +87,7 @@ function SignUp() { @@ -102,6 +102,7 @@ function SignUp() { { + await page.getByPlaceholder("Full Name").fill(full_name) + await page.getByPlaceholder("Email").fill(email) + await page.getByPlaceholder("Password", { exact: true }).fill(password) + await page.getByPlaceholder("Repeat Password").fill(confirm_password) +} + +const verifyInput = async ( + page: Page, + placeholder: string, + options?: OptionsType, +) => { + const input = page.getByPlaceholder(placeholder, options) + await expect(input).toBeVisible() + await expect(input).toHaveText("") + await expect(input).toBeEditable() +} + +test("Inputs are visible, empty and editable", async ({ page }) => { + await page.goto("/signup") + + await verifyInput(page, "Full Name") + await verifyInput(page, "Email") + await verifyInput(page, "Password", { exact: true }) + await verifyInput(page, "Repeat Password") +}) + +test("Sign Up button is visible", async ({ page }) => { + await page.goto("/signup") + + await expect(page.getByRole("button", { name: "Sign Up" })).toBeVisible() +}) + +test("Log In link is visible", async ({ page }) => { + await page.goto("/signup") + + await expect(page.getByRole("link", { name: "Log In" })).toBeVisible() +}) + +test("Sign up with valid name, email, and password", async ({ page }) => { + const full_name = "Test User" + const email = randomEmail() + const password = "changethis" + + await page.goto("/signup") + await fillForm(page, full_name, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() +}) + +test("Sign up with invalid email", async ({ page }) => { + await page.goto("/signup") + + await fillForm( + page, + "Playwright Test", + "invalid-email", + "changethis", + "changethis", + ) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Invalid email address")).toBeVisible() +}) + +test("Sign up with existing email", async ({ page }) => { + const full_name = "Test User" + const email = randomEmail() + const password = "changethis" + + // Sign up with an email + await page.goto("/signup") + + await fillForm(page, full_name, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + // Sign up again with the same email + await page.goto("/signup") + + await fillForm(page, full_name, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await page + .getByText("The user with this email already exists in the system") + .click() +}) + +test("Sign up with weak password", async ({ page }) => { + const full_name = "Test User" + const email = randomEmail() + const password = "weak" + + await page.goto("/signup") + + await fillForm(page, full_name, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect( + page.getByText("Password must be at least 8 characters"), + ).toBeVisible() +}) + +test("Sign up with mismatched passwords", async ({ page }) => { + const full_name = "Test User" + const email = randomEmail() + const password = "changethis" + const password2 = "changethat" + + await page.goto("/signup") + + await fillForm(page, full_name, email, password, password2) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Passwords do not match")).toBeVisible() +}) + +test("Sign up with missing full name", async ({ page }) => { + const full_name = "" + const email = randomEmail() + const password = "changethis" + + await page.goto("/signup") + + await fillForm(page, full_name, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Full Name is required")).toBeVisible() +}) + +test("Sign up with missing email", async ({ page }) => { + const full_name = "Test User" + const email = "" + const password = "changethis" + + await page.goto("/signup") + + await fillForm(page, full_name, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Email is required")).toBeVisible() +}) + +test("Sign up with missing password", async ({ page }) => { + const full_name = "" + const email = randomEmail() + const password = "" + + await page.goto("/signup") + + await fillForm(page, full_name, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Password is required")).toBeVisible() +}) diff --git a/frontend/tests/utils/random.ts b/frontend/tests/utils/random.ts new file mode 100644 index 0000000000..bd2b43ba10 --- /dev/null +++ b/frontend/tests/utils/random.ts @@ -0,0 +1,11 @@ +export const randomEmail = () => + `test_${Math.random().toString(36).substring(7)}@example.com` + +export const randomTeamName = () => + `Team ${Math.random().toString(36).substring(7)}` + +export const slugify = (text: string) => + text + .toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^\w-]+/g, "") From 25f670f8036b50dc662a0398ff4a7707280da17a Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 29 Jul 2024 17:54:02 +0000 Subject: [PATCH 576/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 5e184b8d17..1ab01cf23b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Add Sign Up e2e tests . PR [#1268](https://github.com/tiangolo/full-stack-fastapi-template/pull/1268) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sign Up and make `OPEN_USER_REGISTRATION=True` by default. PR [#1265](https://github.com/tiangolo/full-stack-fastapi-template/pull/1265) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Login e2e tests. PR [#1264](https://github.com/tiangolo/full-stack-fastapi-template/pull/1264) by [@alejsdev](https://github.com/alejsdev). * ✨ Add initial setup for frontend / end-to-end tests with Playwright. PR [#1261](https://github.com/tiangolo/full-stack-fastapi-template/pull/1261) by [@alejsdev](https://github.com/alejsdev). From aa38302c1a70a0f80d8191b1ec2f3a9f0664b882 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:09:57 -0500 Subject: [PATCH 577/771] =?UTF-8?q?=E2=9C=A8=20Add=20Reset=20Password=20e2?= =?UTF-8?q?e=20tests=20(#1270)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useAuth.ts | 6 +- frontend/src/routes/reset-password.tsx | 2 +- frontend/tests/reset-password.spec.ts | 119 +++++++++++++++++++++++++ frontend/tests/utils/mailcatcher.ts | 59 ++++++++++++ frontend/tests/utils/user.ts | 38 ++++++++ 5 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 frontend/tests/reset-password.spec.ts create mode 100644 frontend/tests/utils/mailcatcher.ts create mode 100644 frontend/tests/utils/user.ts diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 6b2499fee8..20f09d1103 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -34,7 +34,11 @@ const useAuth = () => { onSuccess: () => { navigate({ to: "/login" }) - showToast("Success!", "User created successfully.", "success") + showToast( + "Account created.", + "Your account has been created successfully.", + "success", + ) }, onError: (err: ApiError) => { let errDetail = (err.body as any)?.detail diff --git a/frontend/src/routes/reset-password.tsx b/frontend/src/routes/reset-password.tsx index 767451eaae..11bc552d9b 100644 --- a/frontend/src/routes/reset-password.tsx +++ b/frontend/src/routes/reset-password.tsx @@ -60,7 +60,7 @@ function ResetPassword() { const mutation = useMutation({ mutationFn: resetPassword, onSuccess: () => { - showToast("Success!", "Password updated.", "success") + showToast("Success!", "Password updated successfully.", "success") reset() navigate({ to: "/login" }) }, diff --git a/frontend/tests/reset-password.spec.ts b/frontend/tests/reset-password.spec.ts new file mode 100644 index 0000000000..59cbceeefb --- /dev/null +++ b/frontend/tests/reset-password.spec.ts @@ -0,0 +1,119 @@ +import { expect, test } from "@playwright/test" +import { findLastEmail } from "./utils/mailcatcher" +import { randomEmail } from "./utils/random" +import { logInUser, signUpNewUser } from "./utils/user" + +test.use({ storageState: { cookies: [], origins: [] } }) + +test("Password Recovery title is visible", async ({ page }) => { + await page.goto("/recover-password") + + await expect( + page.getByRole("heading", { name: "Password Recovery" }), + ).toBeVisible() +}) + +test("Input is visible, empty and editable", async ({ page }) => { + await page.goto("/recover-password") + + await expect(page.getByPlaceholder("Email")).toBeVisible() + await expect(page.getByPlaceholder("Email")).toHaveText("") + await expect(page.getByPlaceholder("Email")).toBeEditable() +}) + +test("Continue button is visible", async ({ page }) => { + await page.goto("/recover-password") + + await expect(page.getByRole("button", { name: "Continue" })).toBeVisible() +}) + +test("User can reset password successfully using the link", async ({ + page, + request, +}) => { + const full_name = "Test User" + const email = randomEmail() + const password = "changethis" + const new_password = "changethat" + + // Sign up a new user + await signUpNewUser(page, full_name, email, password) + + await page.goto("/recover-password") + await page.getByPlaceholder("Email").fill(email) + + await page.getByRole("button", { name: "Continue" }).click() + + const emailData = await findLastEmail({ + request, + filter: (e) => e.recipients.includes(`<${email}>`), + timeout: 5000, + }) + + await page.goto(`http://localhost:1080/messages/${emailData.id}.html`) + + const selector = 'a[href*="/reset-password?token="]' + + let url = await page.getAttribute(selector, "href") + + // TODO: update var instead of doing a replace + url = url!.replace("http://localhost/", "http://localhost:5173/") + + // Set the new password and confirm it + await page.goto(url) + + await page.getByLabel("Set Password").fill(new_password) + await page.getByLabel("Confirm Password").fill(new_password) + await page.getByRole("button", { name: "Reset Password" }).click() + await expect(page.getByText("Password updated successfully")).toBeVisible() + + // Check if the user is able to login with the new password + await logInUser(page, email, new_password) +}) + +test("Expired or invalid reset link", async ({ page }) => { + const invalidUrl = "/reset-password?token=invalidtoken" + + await page.goto(invalidUrl) + + await page.getByLabel("Set Password").fill("newpassword") + await page.getByLabel("Confirm Password").fill("newpassword") + await page.getByRole("button", { name: "Reset Password" }).click() + + await expect(page.getByText("Invalid token")).toBeVisible() +}) + +test("Weak new password validation", async ({ page, request }) => { + const full_name = "Test User" + const email = randomEmail() + const password = "password" + + // Sign up a new user + await signUpNewUser(page, full_name, email, password) + + await page.goto("/recover-password") + await page.getByPlaceholder("Email").fill(email) + await page.getByRole("button", { name: "Continue" }).click() + + const emailData = await findLastEmail({ + request, + filter: (e) => e.recipients.includes(`<${email}>`), + timeout: 5000, + }) + + await page.goto(`http://localhost:1080/messages/${emailData.id}.html`) + + const selector = 'a[href*="/reset-password?token="]' + let url = await page.getAttribute(selector, "href") + url = url!.replace("http://localhost/", "http://localhost:5173/") + + // Set a weak new password + await page.goto(url) + await page.getByLabel("Set Password").fill("123") + await page.getByLabel("Confirm Password").fill("123") + await page.getByRole("button", { name: "Reset Password" }).click() + + await expect( + page.getByText("Password must be at least 8 characters"), + ).toBeVisible() +}) diff --git a/frontend/tests/utils/mailcatcher.ts b/frontend/tests/utils/mailcatcher.ts new file mode 100644 index 0000000000..601ce434fb --- /dev/null +++ b/frontend/tests/utils/mailcatcher.ts @@ -0,0 +1,59 @@ +import type { APIRequestContext } from "@playwright/test" + +type Email = { + id: number + recipients: string[] + subject: string +} + +async function findEmail({ + request, + filter, +}: { request: APIRequestContext; filter?: (email: Email) => boolean }) { + const response = await request.get("http://localhost:1080/messages") + + let emails = await response.json() + + if (filter) { + emails = emails.filter(filter) + } + + const email = emails[emails.length - 1] + + if (email) { + return email as Email + } + + return null +} + +export function findLastEmail({ + request, + filter, + timeout = 5000, +}: { + request: APIRequestContext + filter?: (email: Email) => boolean + timeout?: number +}) { + const timeoutPromise = new Promise((_, reject) => + setTimeout( + () => reject(new Error("Timeout while trying to get latest email")), + timeout, + ), + ) + + const checkEmails = async () => { + while (true) { + const emailData = await findEmail({ request, filter }) + + if (emailData) { + return emailData + } + // Wait for 100ms before checking again + await new Promise((resolve) => setTimeout(resolve, 100)) + } + } + + return Promise.race([timeoutPromise, checkEmails()]) +} diff --git a/frontend/tests/utils/user.ts b/frontend/tests/utils/user.ts new file mode 100644 index 0000000000..6f02c0e8f3 --- /dev/null +++ b/frontend/tests/utils/user.ts @@ -0,0 +1,38 @@ +import { type Page, expect } from "@playwright/test" + +export async function signUpNewUser( + page: Page, + name: string, + email: string, + password: string, +) { + await page.goto("/signup") + + await page.getByPlaceholder("Full Name").fill(name) + await page.getByPlaceholder("Email").fill(email) + await page.getByPlaceholder("Password", { exact: true }).fill(password) + await page.getByPlaceholder("Repeat Password").fill(password) + await page.getByRole("button", { name: "Sign Up" }).click() + await expect( + page.getByText("Your account has been created successfully"), + ).toBeVisible() + await page.goto("/login") +} + +export async function logInUser(page: Page, email: string, password: string) { + await page.goto("/login") + + await page.getByPlaceholder("Email").fill(email) + await page.getByPlaceholder("Password", { exact: true }).fill(password) + await page.getByRole("button", { name: "Log In" }).click() + await page.waitForURL("/") + await expect( + page.getByText("Welcome back, nice to see you again!"), + ).toBeVisible() +} + +export async function logOutUser(page: Page, name: string) { + await page.getByRole("button", { name: name }).click() + await page.getByRole("menuitem", { name: "Log out" }).click() + await page.goto("/login") +} From 60f26512a265bd6198a0496bc1a5d56f11152a9b Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 30 Jul 2024 14:10:16 +0000 Subject: [PATCH 578/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 1ab01cf23b..0373284608 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Add Reset Password e2e tests. PR [#1270](https://github.com/tiangolo/full-stack-fastapi-template/pull/1270) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sign Up e2e tests . PR [#1268](https://github.com/tiangolo/full-stack-fastapi-template/pull/1268) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sign Up and make `OPEN_USER_REGISTRATION=True` by default. PR [#1265](https://github.com/tiangolo/full-stack-fastapi-template/pull/1265) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Login e2e tests. PR [#1264](https://github.com/tiangolo/full-stack-fastapi-template/pull/1264) by [@alejsdev](https://github.com/alejsdev). From e264d344453df17d1de9cb952099b5d80ce80954 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Tue, 30 Jul 2024 19:46:41 +0200 Subject: [PATCH 579/771] =?UTF-8?q?=F0=9F=94=A7=20Reuse=20database=20url?= =?UTF-8?q?=20from=20config=20in=20alembic=20setup=20(#1229)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/alembic/env.py | 8 ++------ backend/app/core/config.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/backend/app/alembic/env.py b/backend/app/alembic/env.py index 926f119037..7f29c04680 100755 --- a/backend/app/alembic/env.py +++ b/backend/app/alembic/env.py @@ -19,6 +19,7 @@ # target_metadata = None from app.models import SQLModel # noqa +from app.core.config import settings # noqa target_metadata = SQLModel.metadata @@ -29,12 +30,7 @@ def get_url(): - user = os.getenv("POSTGRES_USER", "postgres") - password = os.getenv("POSTGRES_PASSWORD", "") - server = os.getenv("POSTGRES_SERVER", "db") - port = os.getenv("POSTGRES_PORT", "5432") - db = os.getenv("POSTGRES_DB", "app") - return f"postgresql+psycopg://{user}:{password}@{server}:{port}/{db}" + return str(settings.SQLALCHEMY_DATABASE_URI) def run_migrations_offline(): diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 41de80f78d..68cf70292f 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -51,7 +51,7 @@ def server_host(self) -> str: POSTGRES_SERVER: str POSTGRES_PORT: int = 5432 POSTGRES_USER: str - POSTGRES_PASSWORD: str + POSTGRES_PASSWORD: str = "" POSTGRES_DB: str = "" @computed_field # type: ignore[misc] From 526e993783838fc8245dfb8c08d45bdb2aa76add Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 30 Jul 2024 17:47:00 +0000 Subject: [PATCH 580/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 0373284608..84cdf427f4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -26,6 +26,7 @@ ### Refactors +* 🔧 Reuse database url from config in alembic setup. PR [#1229](https://github.com/tiangolo/full-stack-fastapi-template/pull/1229) by [@patrick91](https://github.com/patrick91). * 🔧 Update Playwright config and tests to use env variables. PR [#1266](https://github.com/tiangolo/full-stack-fastapi-template/pull/1266) by [@alejsdev](https://github.com/alejsdev). * ♻️ Edit refactor db models to use UUID's instead of integer ID's. PR [#1259](https://github.com/tiangolo/full-stack-fastapi-template/pull/1259) by [@estebanx64](https://github.com/estebanx64). * ♻️ Update form inputs width. PR [#1263](https://github.com/tiangolo/full-stack-fastapi-template/pull/1263) by [@alejsdev](https://github.com/alejsdev). From 2f47f852fb01836c6eb770f017f16f43affedcc5 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:48:36 -0500 Subject: [PATCH 581/771] =?UTF-8?q?=E2=9C=A8=20Add=20User=20Settings=20e2e?= =?UTF-8?q?=20tests=20=20(#1271)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserSettings/ChangePassword.tsx | 2 +- frontend/tests/user-settings.spec.ts | 288 ++++++++++++++++++ frontend/tests/utils/user.ts | 4 +- 3 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 frontend/tests/user-settings.spec.ts diff --git a/frontend/src/components/UserSettings/ChangePassword.tsx b/frontend/src/components/UserSettings/ChangePassword.tsx index 0698e1a9c8..439ee80867 100644 --- a/frontend/src/components/UserSettings/ChangePassword.tsx +++ b/frontend/src/components/UserSettings/ChangePassword.tsx @@ -38,7 +38,7 @@ const ChangePassword = () => { mutationFn: (data: UpdatePassword) => UsersService.updatePasswordMe({ requestBody: data }), onSuccess: () => { - showToast("Success!", "Password updated.", "success") + showToast("Success!", "Password updated successfully.", "success") reset() }, onError: (err: ApiError) => { diff --git a/frontend/tests/user-settings.spec.ts b/frontend/tests/user-settings.spec.ts new file mode 100644 index 0000000000..b24f1ec5a8 --- /dev/null +++ b/frontend/tests/user-settings.spec.ts @@ -0,0 +1,288 @@ +import { expect, test } from "@playwright/test" +import { randomEmail } from "./utils/random" +import { logInUser, logOutUser, signUpNewUser } from "./utils/user" +import { firstSuperuser, firstSuperuserPassword } from "./config.ts" + +const tabs = ["My profile", "Password", "Appearance"] + +// User Information + +test("My profile tab is active by default", async ({ page }) => { + await page.goto("/settings") + await expect(page.getByRole("tab", { name: "My profile" })).toHaveAttribute( + "aria-selected", + "true", + ) +}) + +test("All tabs are visible", async ({ page }) => { + await page.goto("/settings") + for (const tab of tabs) { + await expect(page.getByRole("tab", { name: tab })).toBeVisible() + } +}) + +test.describe("Edit user full name and email successfully", () => { + test.use({ storageState: { cookies: [], origins: [] } }) + + test("Edit user name with a valid name", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const updatedName = "Test User 2" + const password = "password" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Full name").fill(updatedName) + await page.getByRole("button", { name: "Save" }).click() + await expect(page.getByText("User updated successfully")).toBeVisible() + // Check if the new name is displayed on the page + await expect( + page.getByLabel("My profile").getByText(updatedName, { exact: true }), + ).toBeVisible() + }) + + test("Edit user email with a valid email", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const updatedEmail = randomEmail() + const password = "password" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Email").fill(updatedEmail) + await page.getByRole("button", { name: "Save" }).click() + await expect(page.getByText("User updated successfully")).toBeVisible() + await expect( + page.getByLabel("My profile").getByText(updatedEmail, { exact: true }), + ).toBeVisible() + }) +}) + +test.describe("Edit user with invalid data", () => { + test.use({ storageState: { cookies: [], origins: [] } }) + + test("Edit user email with an invalid email", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = "password" + const invalidEmail = "" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Email").fill(invalidEmail) + await page.locator("body").click() + await expect(page.getByText("Email is required")).toBeVisible() + }) + + test("Cancel edit action restores original name", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = "password" + const updatedName = "Test User" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Full name").fill(updatedName) + await page.getByRole("button", { name: "Cancel" }).first().click() + await expect( + page.getByLabel("My profile").getByText(fullName, { exact: true }), + ).toBeVisible() + }) + + test("Cancel edit action restores original email", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = "password" + const updatedEmail = randomEmail() + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Email").fill(updatedEmail) + await page.getByRole("button", { name: "Cancel" }).first().click() + await expect( + page.getByLabel("My profile").getByText(email, { exact: true }), + ).toBeVisible() + }) +}) + +// Change Password + +test.describe("Change password successfully", () => { + test.use({ storageState: { cookies: [], origins: [] } }) + + test("Update password successfully", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = "password" + const NewPassword = "newPassword" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "Password" }).click() + await page.getByLabel("Current Password*").fill(password) + await page.getByLabel("Set Password*").fill(NewPassword) + await page.getByLabel("Confirm Password*").fill(NewPassword) + await page.getByRole("button", { name: "Save" }).click() + await expect(page.getByText("Password updated successfully.")).toBeVisible() + + await logOutUser(page) + + // Check if the user can log in with the new password + await logInUser(page, email, NewPassword) + }) +}) + +test.describe("Change password with invalid data", () => { + test.use({ storageState: { cookies: [], origins: [] } }) + + test("Update password with weak passwords", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = "password" + const weakPassword = "weak" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "Password" }).click() + await page.getByLabel("Current Password*").fill(password) + await page.getByLabel("Set Password*").fill(weakPassword) + await page.getByLabel("Confirm Password*").fill(weakPassword) + await expect( + page.getByText("Password must be at least 8 characters"), + ).toBeVisible() + }) + + test("New password and confirmation password do not match", async ({ + page, + }) => { + const fullName = "Test User" + const email = randomEmail() + const password = "password" + const newPassword = "newPassword" + const confirmPassword = "confirmPassword" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "Password" }).click() + await page.getByLabel("Current Password*").fill(password) + await page.getByLabel("Set Password*").fill(newPassword) + await page.getByLabel("Confirm Password*").fill(confirmPassword) + await page.getByRole("button", { name: "Save" }).click() + await expect(page.getByText("Passwords do not match")).toBeVisible() + }) + + test("Current password and new password are the same", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = "password" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "Password" }).click() + await page.getByLabel("Current Password*").fill(password) + await page.getByLabel("Set Password*").fill(password) + await page.getByLabel("Confirm Password*").fill(password) + await page.getByRole("button", { name: "Save" }).click() + await expect( + page.getByText("New password cannot be the same as the current one"), + ).toBeVisible() + }) +}) + +// Appearance + +test("Appearance tab is visible", async ({ page }) => { + await page.goto("/settings") + await page.getByRole("tab", { name: "Appearance" }).click() + await expect(page.getByLabel("Appearance")).toBeVisible() +}) + +test("User can switch from light mode to dark mode", async ({ page }) => { + await page.goto("/settings") + await page.getByRole("tab", { name: "Appearance" }).click() + await page.getByLabel("Appearance").locator("span").nth(3).click() + const isDarkMode = await page.evaluate(() => + document.body.classList.contains("chakra-ui-dark"), + ) + expect(isDarkMode).toBe(true) +}) + +test("User can switch from dark mode to light mode", async ({ page }) => { + await page.goto("/settings") + await page.getByRole("tab", { name: "Appearance" }).click() + await page.getByLabel("Appearance").locator("span").first().click() + const isLightMode = await page.evaluate(() => + document.body.classList.contains("chakra-ui-light"), + ) + expect(isLightMode).toBe(true) +}) + +test("Selected mode is preserved across sessions", async ({ page }) => { + await page.goto("/settings") + await page.getByRole("tab", { name: "Appearance" }).click() + await page.getByLabel("Appearance").locator("span").nth(3).click() + + await logOutUser(page) + + await logInUser(page, firstSuperuser, firstSuperuserPassword) + const isDarkMode = await page.evaluate(() => + document.body.classList.contains("chakra-ui-dark"), + ) + expect(isDarkMode).toBe(true) +}) diff --git a/frontend/tests/utils/user.ts b/frontend/tests/utils/user.ts index 6f02c0e8f3..8fcfd26cb5 100644 --- a/frontend/tests/utils/user.ts +++ b/frontend/tests/utils/user.ts @@ -31,8 +31,8 @@ export async function logInUser(page: Page, email: string, password: string) { ).toBeVisible() } -export async function logOutUser(page: Page, name: string) { - await page.getByRole("button", { name: name }).click() +export async function logOutUser(page: Page) { + await page.getByTestId("user-menu").click() await page.getByRole("menuitem", { name: "Log out" }).click() await page.goto("/login") } From 397f44633a57c1dec92d02ca6244c16ec7224e2a Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 30 Jul 2024 17:48:55 +0000 Subject: [PATCH 582/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 84cdf427f4..2dfbc0c214 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Add User Settings e2e tests . PR [#1271](https://github.com/tiangolo/full-stack-fastapi-template/pull/1271) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Reset Password e2e tests. PR [#1270](https://github.com/tiangolo/full-stack-fastapi-template/pull/1270) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sign Up e2e tests . PR [#1268](https://github.com/tiangolo/full-stack-fastapi-template/pull/1268) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sign Up and make `OPEN_USER_REGISTRATION=True` by default. PR [#1265](https://github.com/tiangolo/full-stack-fastapi-template/pull/1265) by [@alejsdev](https://github.com/alejsdev). From ebf85bf0a202866e81e858963795ba3d16d78007 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:08:26 -0500 Subject: [PATCH 583/771] =?UTF-8?q?=F0=9F=94=A5=20Remove=20`USERS=5FOPEN?= =?UTF-8?q?=5FREGISTRATION`=20config,=20make=20registration=20enabled=20by?= =?UTF-8?q?=20default=20(#1274)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/README.md | 10 --- backend/app/api/routes/users.py | 5 -- backend/app/core/config.py | 1 - backend/app/tests/api/routes/test_users.py | 83 ++++++++-------------- deployment.md | 1 - docker-compose.yml | 1 - 6 files changed, 31 insertions(+), 70 deletions(-) diff --git a/backend/README.md b/backend/README.md index 27aabab578..e6400be194 100644 --- a/backend/README.md +++ b/backend/README.md @@ -63,16 +63,6 @@ Make sure your editor is using the correct Python virtual environment. Modify or add SQLModel models for data and SQL tables in `./backend/app/models.py`, API endpoints in `./backend/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/crud.py`. -### Enabling Open User Registration - -By default the backend has user registration disabled, but there's already a route to register users. If you want to allow users to register themselves, you can set the environment variable `USERS_OPEN_REGISTRATION` to `True` in the `.env` file. - -After modifying the environment variables, restart the Docker containers to apply the changes. You can do this by running: - -```console -$ docker compose up -d -``` - ### VS Code There are already configurations in place to run the backend through the VS Code debugger, so that you can use breakpoints, pause and explore variables, etc. diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 21c30f1a41..c636b094ee 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -146,11 +146,6 @@ def register_user(session: SessionDep, user_in: UserRegister) -> Any: """ Create new user without the need to be logged in. """ - if not settings.USERS_OPEN_REGISTRATION: - raise HTTPException( - status_code=403, - detail="Open user registration is forbidden on this server", - ) user = crud.get_user_by_email(session=session, email=user_in.email) if user: raise HTTPException( diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 68cf70292f..3a78a5e540 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -94,7 +94,6 @@ def emails_enabled(self) -> bool: # TODO: update type to EmailStr when sqlmodel supports it FIRST_SUPERUSER: str FIRST_SUPERUSER_PASSWORD: str - USERS_OPEN_REGISTRATION: bool = False def _check_default_secret(self, var_name: str, value: str | None) -> None: if value == "changethis": diff --git a/backend/app/tests/api/routes/test_users.py b/backend/app/tests/api/routes/test_users.py index d5daf9e934..ba9be65426 100644 --- a/backend/app/tests/api/routes/test_users.py +++ b/backend/app/tests/api/routes/test_users.py @@ -283,62 +283,41 @@ def test_update_password_me_same_password_error( def test_register_user(client: TestClient, db: Session) -> None: - with patch("app.core.config.settings.USERS_OPEN_REGISTRATION", True): - username = random_email() - password = random_lower_string() - full_name = random_lower_string() - data = {"email": username, "password": password, "full_name": full_name} - r = client.post( - f"{settings.API_V1_STR}/users/signup", - json=data, - ) - assert r.status_code == 200 - created_user = r.json() - assert created_user["email"] == username - assert created_user["full_name"] == full_name - - user_query = select(User).where(User.email == username) - user_db = db.exec(user_query).first() - assert user_db - assert user_db.email == username - assert user_db.full_name == full_name - assert verify_password(password, user_db.hashed_password) - + username = random_email() + password = random_lower_string() + full_name = random_lower_string() + data = {"email": username, "password": password, "full_name": full_name} + r = client.post( + f"{settings.API_V1_STR}/users/signup", + json=data, + ) + assert r.status_code == 200 + created_user = r.json() + assert created_user["email"] == username + assert created_user["full_name"] == full_name -def test_register_user_forbidden_error(client: TestClient) -> None: - with patch("app.core.config.settings.USERS_OPEN_REGISTRATION", False): - username = random_email() - password = random_lower_string() - full_name = random_lower_string() - data = {"email": username, "password": password, "full_name": full_name} - r = client.post( - f"{settings.API_V1_STR}/users/signup", - json=data, - ) - assert r.status_code == 403 - assert ( - r.json()["detail"] == "Open user registration is forbidden on this server" - ) + user_query = select(User).where(User.email == username) + user_db = db.exec(user_query).first() + assert user_db + assert user_db.email == username + assert user_db.full_name == full_name + assert verify_password(password, user_db.hashed_password) def test_register_user_already_exists_error(client: TestClient) -> None: - with patch("app.core.config.settings.USERS_OPEN_REGISTRATION", True): - password = random_lower_string() - full_name = random_lower_string() - data = { - "email": settings.FIRST_SUPERUSER, - "password": password, - "full_name": full_name, - } - r = client.post( - f"{settings.API_V1_STR}/users/signup", - json=data, - ) - assert r.status_code == 400 - assert ( - r.json()["detail"] - == "The user with this email already exists in the system" - ) + password = random_lower_string() + full_name = random_lower_string() + data = { + "email": settings.FIRST_SUPERUSER, + "password": password, + "full_name": full_name, + } + r = client.post( + f"{settings.API_V1_STR}/users/signup", + json=data, + ) + assert r.status_code == 400 + assert r.json()["detail"] == "The user with this email already exists in the system" def test_update_user( diff --git a/deployment.md b/deployment.md index ab5aaedb9c..6bcbe40259 100644 --- a/deployment.md +++ b/deployment.md @@ -133,7 +133,6 @@ You can set several variables, like: * `SECRET_KEY`: The secret key for the FastAPI project, used to sign tokens. * `FIRST_SUPERUSER`: The email of the first superuser, this superuser will be the one that can create new users. * `FIRST_SUPERUSER_PASSWORD`: The password of the first superuser. -* `USERS_OPEN_REGISTRATION`: Whether to allow open registration of new users. * `SMTP_HOST`: The SMTP server host to send emails, this would come from your email provider (E.g. Mailgun, Sparkpost, Sendgrid, etc). * `SMTP_USER`: The SMTP server user to send emails. * `SMTP_PASSWORD`: The SMTP server password to send emails. diff --git a/docker-compose.yml b/docker-compose.yml index 9d1c6bb438..d614942cbd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,7 +52,6 @@ services: - SECRET_KEY=${SECRET_KEY?Variable not set} - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set} - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set} - - USERS_OPEN_REGISTRATION=${USERS_OPEN_REGISTRATION} - SMTP_HOST=${SMTP_HOST} - SMTP_USER=${SMTP_USER} - SMTP_PASSWORD=${SMTP_PASSWORD} From ac9fe20da09dce7e6a2e26795184266474d42333 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 31 Jul 2024 22:08:46 +0000 Subject: [PATCH 584/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 2dfbc0c214..bbaa86f025 100644 --- a/release-notes.md +++ b/release-notes.md @@ -27,6 +27,7 @@ ### Refactors +* 🔥 Remove `USERS_OPEN_REGISTRATION` config, make registration enabled by default. PR [#1274](https://github.com/tiangolo/full-stack-fastapi-template/pull/1274) by [@alejsdev](https://github.com/alejsdev). * 🔧 Reuse database url from config in alembic setup. PR [#1229](https://github.com/tiangolo/full-stack-fastapi-template/pull/1229) by [@patrick91](https://github.com/patrick91). * 🔧 Update Playwright config and tests to use env variables. PR [#1266](https://github.com/tiangolo/full-stack-fastapi-template/pull/1266) by [@alejsdev](https://github.com/alejsdev). * ♻️ Edit refactor db models to use UUID's instead of integer ID's. PR [#1259](https://github.com/tiangolo/full-stack-fastapi-template/pull/1259) by [@estebanx64](https://github.com/estebanx64). From 0354a458e04fe7516ce0b300f213557362985b0c Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:14:04 -0500 Subject: [PATCH 585/771] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20SQLModel?= =?UTF-8?q?=20to=20version=20`>=3D0.0.21`=20(#1275)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/core/config.py | 6 +-- backend/poetry.lock | 100 ++++++++++++++++--------------------- backend/pyproject.toml | 2 +- 3 files changed, 47 insertions(+), 61 deletions(-) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 3a78a5e540..1e3a440c1c 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -34,7 +34,7 @@ class Settings(BaseSettings): DOMAIN: str = "localhost" ENVIRONMENT: Literal["local", "staging", "production"] = "local" - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def server_host(self) -> str: # Use HTTPS for anything other than local development @@ -54,7 +54,7 @@ def server_host(self) -> str: POSTGRES_PASSWORD: str = "" POSTGRES_DB: str = "" - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn: return MultiHostUrl.build( @@ -84,7 +84,7 @@ def _set_default_emails_from(self) -> Self: EMAIL_RESET_TOKEN_EXPIRE_HOURS: int = 48 - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def emails_enabled(self) -> bool: return bool(self.SMTP_HOST and self.EMAILS_FROM_EMAIL) diff --git a/backend/poetry.lock b/backend/poetry.lock index af32d87a20..f56ce480f0 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -969,44 +969,44 @@ files = [ [[package]] name = "mypy" -version = "1.10.1" +version = "1.11.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, - {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, - {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, - {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, - {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, - {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, - {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, - {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, - {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, - {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, - {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, - {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, - {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, - {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, - {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, - {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, - {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, - {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, - {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, + {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, + {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, + {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, + {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, + {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, + {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, + {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, + {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, + {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, + {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, + {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, + {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, + {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, + {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, + {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, + {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, + {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" +typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -1100,13 +1100,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.7.1" +version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, - {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, + {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, + {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, ] [package.dependencies] @@ -1349,13 +1349,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.3.4" +version = "2.4.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.3.4-py3-none-any.whl", hash = "sha256:11ad8bacb68a045f00e4f862c7a718c8a9ec766aa8fd4c32e39a0594b207b53a"}, - {file = "pydantic_settings-2.3.4.tar.gz", hash = "sha256:c5802e3d62b78e82522319bbc9b8f8ffb28ad1c988a99311d04f2a6051fca0a7"}, + {file = "pydantic_settings-2.4.0-py3-none-any.whl", hash = "sha256:bb6849dc067f1687574c12a639e231f3a6feeed0a12d710c1382045c5db1c315"}, + {file = "pydantic_settings-2.4.0.tar.gz", hash = "sha256:ed81c3a0f46392b4d7c0a565c05884e6e54b3456e6f0fe4d8814981172dc9a88"}, ] [package.dependencies] @@ -1363,6 +1363,7 @@ pydantic = ">=2.7.0" python-dotenv = ">=0.21.0" [package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] @@ -1556,13 +1557,13 @@ files = [ [[package]] name = "sentry-sdk" -version = "1.45.0" +version = "1.45.1" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.45.0.tar.gz", hash = "sha256:509aa9678c0512344ca886281766c2e538682f8acfa50fd8d405f8c417ad0625"}, - {file = "sentry_sdk-1.45.0-py2.py3-none-any.whl", hash = "sha256:1ce29e30240cc289a027011103a8c83885b15ef2f316a60bcc7c5300afa144f1"}, + {file = "sentry_sdk-1.45.1-py2.py3-none-any.whl", hash = "sha256:608887855ccfe39032bfd03936e3a1c4f4fc99b3a4ac49ced54a4220de61c9c1"}, + {file = "sentry_sdk-1.45.1.tar.gz", hash = "sha256:a16c997c0f4e3df63c0fc5e4207ccb1ab37900433e0f72fef88315d317829a26"}, ] [package.dependencies] @@ -1602,21 +1603,6 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] -[[package]] -name = "setuptools" -version = "70.0.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -1728,13 +1714,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlmodel" -version = "0.0.20" +version = "0.0.21" description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." optional = false python-versions = ">=3.7" files = [ - {file = "sqlmodel-0.0.20-py3-none-any.whl", hash = "sha256:744756c49e24095808984754cc4d3a32c2d8361fef803c4914fadcb912239bc9"}, - {file = "sqlmodel-0.0.20.tar.gz", hash = "sha256:94dd1f63e4ceb0ab405e304e1ad3e8b8c8800b47c3ca5f68736807be8e5b9314"}, + {file = "sqlmodel-0.0.21-py3-none-any.whl", hash = "sha256:eca104afe8a643f0764076b29f02e51d19d6b35c458f4c119942960362a4b52a"}, + {file = "sqlmodel-0.0.21.tar.gz", hash = "sha256:b2034c23d930f66d2091b17a4280a9c23a7ea540a71e7fcf9c746d262f06f74a"}, ] [package.dependencies] @@ -2095,4 +2081,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "bf09330cd76ebeb0e198c25ab36664130490e052d66aedc20e7de7ba240681d5" +content-hash = "7ec220bee66b5bc207f9a8b2f4ca9100da0213bb9d0a407b51cac3dc8201e97c" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 06076c5637..671a864645 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -20,7 +20,7 @@ jinja2 = "^3.1.4" alembic = "^1.12.1" httpx = "^0.25.1" psycopg = {extras = ["binary"], version = "^3.1.13"} -sqlmodel = "^0.0.20" +sqlmodel = "^0.0.21" # Pin bcrypt until passlib supports the latest bcrypt = "4.0.1" pydantic-settings = "^2.2.1" From 997947ce6755153090f54e2125e506bf2fd9dea0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 31 Jul 2024 22:14:21 +0000 Subject: [PATCH 586/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index bbaa86f025..6594fccf4a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -78,6 +78,7 @@ ### Upgrades +* ⬆️ Update SQLModel to version `>=0.0.21`. PR [#1275](https://github.com/tiangolo/full-stack-fastapi-template/pull/1275) by [@alejsdev](https://github.com/alejsdev). * ⬆️ Upgrade Traefik. PR [#1241](https://github.com/tiangolo/full-stack-fastapi-template/pull/1241) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Bump requests from 2.31.0 to 2.32.0 in /backend. PR [#1211](https://github.com/tiangolo/full-stack-fastapi-template/pull/1211) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆️ Bump jinja2 from 3.1.3 to 3.1.4 in /backend. PR [#1196](https://github.com/tiangolo/full-stack-fastapi-template/pull/1196) by [@dependabot[bot]](https://github.com/apps/dependabot). From d35215f85e88b2139c6d8a3cd3987a41e6d6d233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 31 Jul 2024 20:59:45 -0500 Subject: [PATCH 587/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20issue-manager.y?= =?UTF-8?q?ml=20GitHub=20Action=20permissions=20(#1278)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/issue-manager.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index a96f24ab43..e491dce6f2 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -11,6 +11,9 @@ on: types: - labeled +permissions: + issues: write + jobs: issue-manager: runs-on: ubuntu-latest From ad3959c2928f258a3b41e8233ceadd50ce8cc9a4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 1 Aug 2024 02:00:15 +0000 Subject: [PATCH 588/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 6594fccf4a..65bd5e9c22 100644 --- a/release-notes.md +++ b/release-notes.md @@ -106,6 +106,7 @@ ### Internal +* 👷 Update issue-manager.yml GitHub Action permissions. PR [#1278](https://github.com/tiangolo/full-stack-fastapi-template/pull/1278) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Bump setuptools from 69.1.1 to 70.0.0 in /backend. PR [#1255](https://github.com/tiangolo/full-stack-fastapi-template/pull/1255) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆️ Bump certifi from 2024.2.2 to 2024.7.4 in /backend. PR [#1250](https://github.com/tiangolo/full-stack-fastapi-template/pull/1250) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆️ Bump urllib3 from 2.2.1 to 2.2.2 in /backend. PR [#1235](https://github.com/tiangolo/full-stack-fastapi-template/pull/1235) by [@dependabot[bot]](https://github.com/apps/dependabot). From 45f64c41d23d0e3e3dce66b0c83cbac359d5de82 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:44:00 -0500 Subject: [PATCH 589/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20models?= =?UTF-8?q?=20to=20use=20cascade=20delete=20relationships=20(#1276)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...608336_add_cascade_delete_relationships.py | 37 +++++++++++++++++++ backend/app/models.py | 6 ++- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py diff --git a/backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py b/backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py new file mode 100644 index 0000000000..10e47a1456 --- /dev/null +++ b/backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py @@ -0,0 +1,37 @@ +"""Add cascade delete relationships + +Revision ID: 1a31ce608336 +Revises: d98dd8ec85a3 +Create Date: 2024-07-31 22:24:34.447891 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = '1a31ce608336' +down_revision = 'd98dd8ec85a3' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('item', 'owner_id', + existing_type=sa.UUID(), + nullable=False) + op.drop_constraint('item_owner_id_fkey', 'item', type_='foreignkey') + op.create_foreign_key(None, 'item', 'user', ['owner_id'], ['id'], ondelete='CASCADE') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'item', type_='foreignkey') + op.create_foreign_key('item_owner_id_fkey', 'item', 'user', ['owner_id'], ['id']) + op.alter_column('item', 'owner_id', + existing_type=sa.UUID(), + nullable=True) + # ### end Alembic commands ### diff --git a/backend/app/models.py b/backend/app/models.py index e649746a44..3472327658 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -43,7 +43,7 @@ class UpdatePassword(SQLModel): class User(UserBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) hashed_password: str - items: list["Item"] = Relationship(back_populates="owner") + items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True) # Properties to return via API, id is always required @@ -76,7 +76,9 @@ class ItemUpdate(ItemBase): class Item(ItemBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) title: str = Field(max_length=255) - owner_id: uuid.UUID = Field(foreign_key="user.id", nullable=False) + owner_id: uuid.UUID = Field( + foreign_key="user.id", nullable=False, ondelete="CASCADE" + ) owner: User | None = Relationship(back_populates="items") From 34bf99d685e573436b3ab4984b3e20cfa940740e Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 1 Aug 2024 16:44:17 +0000 Subject: [PATCH 590/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 65bd5e9c22..d02f8fa177 100644 --- a/release-notes.md +++ b/release-notes.md @@ -27,6 +27,7 @@ ### Refactors +* ♻️ Refactor models to use cascade delete relationships . PR [#1276](https://github.com/tiangolo/full-stack-fastapi-template/pull/1276) by [@alejsdev](https://github.com/alejsdev). * 🔥 Remove `USERS_OPEN_REGISTRATION` config, make registration enabled by default. PR [#1274](https://github.com/tiangolo/full-stack-fastapi-template/pull/1274) by [@alejsdev](https://github.com/alejsdev). * 🔧 Reuse database url from config in alembic setup. PR [#1229](https://github.com/tiangolo/full-stack-fastapi-template/pull/1229) by [@patrick91](https://github.com/patrick91). * 🔧 Update Playwright config and tests to use env variables. PR [#1266](https://github.com/tiangolo/full-stack-fastapi-template/pull/1266) by [@alejsdev](https://github.com/alejsdev). From 86e93fed1b029ff634ea60843307d07c1432e20e Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:45:05 -0500 Subject: [PATCH 591/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20random=20pas?= =?UTF-8?q?sword=20util=20and=20refactor=20tests=20(#1277)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/tests/login.spec.ts | 6 ++-- frontend/tests/reset-password.spec.ts | 32 +++++++++++---------- frontend/tests/sign-up.spec.ts | 40 +++++++++++++-------------- frontend/tests/user-settings.spec.ts | 28 +++++++++---------- frontend/tests/utils/random.ts | 2 ++ 5 files changed, 57 insertions(+), 51 deletions(-) diff --git a/frontend/tests/login.spec.ts b/frontend/tests/login.spec.ts index ed1a8506dc..97c2284f40 100644 --- a/frontend/tests/login.spec.ts +++ b/frontend/tests/login.spec.ts @@ -1,5 +1,6 @@ import { type Page, expect, test } from "@playwright/test" import { firstSuperuser, firstSuperuserPassword } from "./config.ts" +import { randomPassword } from "./utils/random.ts" test.use({ storageState: { cookies: [], origins: [] } }) @@ -67,9 +68,10 @@ test("Log in with invalid email", async ({ page }) => { }) test("Log in with invalid password", async ({ page }) => { + const password = randomPassword() + await page.goto("/login") - // TODO: Add a random password utility - await fillForm(page, firstSuperuser, "changethat") + await fillForm(page, firstSuperuser, password) await page.getByRole("button", { name: "Log In" }).click() await expect(page.getByText("Incorrect email or password")).toBeVisible() diff --git a/frontend/tests/reset-password.spec.ts b/frontend/tests/reset-password.spec.ts index 59cbceeefb..88ec798791 100644 --- a/frontend/tests/reset-password.spec.ts +++ b/frontend/tests/reset-password.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test" import { findLastEmail } from "./utils/mailcatcher" -import { randomEmail } from "./utils/random" +import { randomEmail, randomPassword } from "./utils/random" import { logInUser, signUpNewUser } from "./utils/user" test.use({ storageState: { cookies: [], origins: [] } }) @@ -31,13 +31,13 @@ test("User can reset password successfully using the link", async ({ page, request, }) => { - const full_name = "Test User" + const fullName = "Test User" const email = randomEmail() - const password = "changethis" - const new_password = "changethat" + const password = randomPassword() + const newPassword = randomPassword() // Sign up a new user - await signUpNewUser(page, full_name, email, password) + await signUpNewUser(page, fullName, email, password) await page.goto("/recover-password") await page.getByPlaceholder("Email").fill(email) @@ -62,34 +62,36 @@ test("User can reset password successfully using the link", async ({ // Set the new password and confirm it await page.goto(url) - await page.getByLabel("Set Password").fill(new_password) - await page.getByLabel("Confirm Password").fill(new_password) + await page.getByLabel("Set Password").fill(newPassword) + await page.getByLabel("Confirm Password").fill(newPassword) await page.getByRole("button", { name: "Reset Password" }).click() await expect(page.getByText("Password updated successfully")).toBeVisible() // Check if the user is able to login with the new password - await logInUser(page, email, new_password) + await logInUser(page, email, newPassword) }) test("Expired or invalid reset link", async ({ page }) => { + const password = randomPassword() const invalidUrl = "/reset-password?token=invalidtoken" await page.goto(invalidUrl) - await page.getByLabel("Set Password").fill("newpassword") - await page.getByLabel("Confirm Password").fill("newpassword") + await page.getByLabel("Set Password").fill(password) + await page.getByLabel("Confirm Password").fill(password) await page.getByRole("button", { name: "Reset Password" }).click() await expect(page.getByText("Invalid token")).toBeVisible() }) test("Weak new password validation", async ({ page, request }) => { - const full_name = "Test User" + const fullName = "Test User" const email = randomEmail() - const password = "password" + const password = randomPassword() + const weakPassword = "123" // Sign up a new user - await signUpNewUser(page, full_name, email, password) + await signUpNewUser(page, fullName, email, password) await page.goto("/recover-password") await page.getByPlaceholder("Email").fill(email) @@ -109,8 +111,8 @@ test("Weak new password validation", async ({ page, request }) => { // Set a weak new password await page.goto(url) - await page.getByLabel("Set Password").fill("123") - await page.getByLabel("Confirm Password").fill("123") + await page.getByLabel("Set Password").fill(weakPassword) + await page.getByLabel("Confirm Password").fill(weakPassword) await page.getByRole("button", { name: "Reset Password" }).click() await expect( diff --git a/frontend/tests/sign-up.spec.ts b/frontend/tests/sign-up.spec.ts index 3f08e92d57..a666123280 100644 --- a/frontend/tests/sign-up.spec.ts +++ b/frontend/tests/sign-up.spec.ts @@ -1,6 +1,6 @@ import { type Page, expect, test } from "@playwright/test" -import { randomEmail } from "./utils/random" +import { randomEmail, randomPassword } from "./utils/random" test.use({ storageState: { cookies: [], origins: [] } }) @@ -56,7 +56,7 @@ test("Log In link is visible", async ({ page }) => { test("Sign up with valid name, email, and password", async ({ page }) => { const full_name = "Test User" const email = randomEmail() - const password = "changethis" + const password = randomPassword() await page.goto("/signup") await fillForm(page, full_name, email, password, password) @@ -79,20 +79,20 @@ test("Sign up with invalid email", async ({ page }) => { }) test("Sign up with existing email", async ({ page }) => { - const full_name = "Test User" + const fullName = "Test User" const email = randomEmail() - const password = "changethis" + const password = randomPassword() // Sign up with an email await page.goto("/signup") - await fillForm(page, full_name, email, password, password) + await fillForm(page, fullName, email, password, password) await page.getByRole("button", { name: "Sign Up" }).click() // Sign up again with the same email await page.goto("/signup") - await fillForm(page, full_name, email, password, password) + await fillForm(page, fullName, email, password, password) await page.getByRole("button", { name: "Sign Up" }).click() await page @@ -101,13 +101,13 @@ test("Sign up with existing email", async ({ page }) => { }) test("Sign up with weak password", async ({ page }) => { - const full_name = "Test User" + const fullName = "Test User" const email = randomEmail() const password = "weak" await page.goto("/signup") - await fillForm(page, full_name, email, password, password) + await fillForm(page, fullName, email, password, password) await page.getByRole("button", { name: "Sign Up" }).click() await expect( @@ -116,53 +116,53 @@ test("Sign up with weak password", async ({ page }) => { }) test("Sign up with mismatched passwords", async ({ page }) => { - const full_name = "Test User" + const fullName = "Test User" const email = randomEmail() - const password = "changethis" - const password2 = "changethat" + const password = randomPassword() + const password2 = randomPassword() await page.goto("/signup") - await fillForm(page, full_name, email, password, password2) + await fillForm(page, fullName, email, password, password2) await page.getByRole("button", { name: "Sign Up" }).click() await expect(page.getByText("Passwords do not match")).toBeVisible() }) test("Sign up with missing full name", async ({ page }) => { - const full_name = "" + const fullName = "" const email = randomEmail() - const password = "changethis" + const password = randomPassword() await page.goto("/signup") - await fillForm(page, full_name, email, password, password) + await fillForm(page, fullName, email, password, password) await page.getByRole("button", { name: "Sign Up" }).click() await expect(page.getByText("Full Name is required")).toBeVisible() }) test("Sign up with missing email", async ({ page }) => { - const full_name = "Test User" + const fullName = "Test User" const email = "" - const password = "changethis" + const password = randomPassword() await page.goto("/signup") - await fillForm(page, full_name, email, password, password) + await fillForm(page, fullName, email, password, password) await page.getByRole("button", { name: "Sign Up" }).click() await expect(page.getByText("Email is required")).toBeVisible() }) test("Sign up with missing password", async ({ page }) => { - const full_name = "" + const fullName = "" const email = randomEmail() const password = "" await page.goto("/signup") - await fillForm(page, full_name, email, password, password) + await fillForm(page, fullName, email, password, password) await page.getByRole("button", { name: "Sign Up" }).click() await expect(page.getByText("Password is required")).toBeVisible() diff --git a/frontend/tests/user-settings.spec.ts b/frontend/tests/user-settings.spec.ts index b24f1ec5a8..a3a8a27490 100644 --- a/frontend/tests/user-settings.spec.ts +++ b/frontend/tests/user-settings.spec.ts @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test" -import { randomEmail } from "./utils/random" -import { logInUser, logOutUser, signUpNewUser } from "./utils/user" import { firstSuperuser, firstSuperuserPassword } from "./config.ts" +import { randomEmail, randomPassword } from "./utils/random" +import { logInUser, logOutUser, signUpNewUser } from "./utils/user" const tabs = ["My profile", "Password", "Appearance"] @@ -29,7 +29,7 @@ test.describe("Edit user full name and email successfully", () => { const fullName = "Test User" const email = randomEmail() const updatedName = "Test User 2" - const password = "password" + const password = randomPassword() // Sign up a new user await signUpNewUser(page, fullName, email, password) @@ -53,7 +53,7 @@ test.describe("Edit user full name and email successfully", () => { const fullName = "Test User" const email = randomEmail() const updatedEmail = randomEmail() - const password = "password" + const password = randomPassword() // Sign up a new user await signUpNewUser(page, fullName, email, password) @@ -79,7 +79,7 @@ test.describe("Edit user with invalid data", () => { test("Edit user email with an invalid email", async ({ page }) => { const fullName = "Test User" const email = randomEmail() - const password = "password" + const password = randomPassword() const invalidEmail = "" // Sign up a new user @@ -99,7 +99,7 @@ test.describe("Edit user with invalid data", () => { test("Cancel edit action restores original name", async ({ page }) => { const fullName = "Test User" const email = randomEmail() - const password = "password" + const password = randomPassword() const updatedName = "Test User" // Sign up a new user @@ -121,7 +121,7 @@ test.describe("Edit user with invalid data", () => { test("Cancel edit action restores original email", async ({ page }) => { const fullName = "Test User" const email = randomEmail() - const password = "password" + const password = randomPassword() const updatedEmail = randomEmail() // Sign up a new user @@ -149,8 +149,8 @@ test.describe("Change password successfully", () => { test("Update password successfully", async ({ page }) => { const fullName = "Test User" const email = randomEmail() - const password = "password" - const NewPassword = "newPassword" + const password = randomPassword() + const NewPassword = randomPassword() // Sign up a new user await signUpNewUser(page, fullName, email, password) @@ -179,7 +179,7 @@ test.describe("Change password with invalid data", () => { test("Update password with weak passwords", async ({ page }) => { const fullName = "Test User" const email = randomEmail() - const password = "password" + const password = randomPassword() const weakPassword = "weak" // Sign up a new user @@ -203,9 +203,9 @@ test.describe("Change password with invalid data", () => { }) => { const fullName = "Test User" const email = randomEmail() - const password = "password" - const newPassword = "newPassword" - const confirmPassword = "confirmPassword" + const password = randomPassword() + const newPassword = randomPassword() + const confirmPassword = randomPassword() // Sign up a new user await signUpNewUser(page, fullName, email, password) @@ -225,7 +225,7 @@ test.describe("Change password with invalid data", () => { test("Current password and new password are the same", async ({ page }) => { const fullName = "Test User" const email = randomEmail() - const password = "password" + const password = randomPassword() // Sign up a new user await signUpNewUser(page, fullName, email, password) diff --git a/frontend/tests/utils/random.ts b/frontend/tests/utils/random.ts index bd2b43ba10..d96f0833ce 100644 --- a/frontend/tests/utils/random.ts +++ b/frontend/tests/utils/random.ts @@ -4,6 +4,8 @@ export const randomEmail = () => export const randomTeamName = () => `Team ${Math.random().toString(36).substring(7)}` +export const randomPassword = () => `${Math.random().toString(36).substring(2)}` + export const slugify = (text: string) => text .toLowerCase() From 907037a9e4014ca8e376491fdbf2808a4c6b6ab9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 1 Aug 2024 16:45:25 +0000 Subject: [PATCH 592/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index d02f8fa177..02d275b8df 100644 --- a/release-notes.md +++ b/release-notes.md @@ -27,6 +27,7 @@ ### Refactors +* ♻️ Add random password util and refactor tests. PR [#1277](https://github.com/tiangolo/full-stack-fastapi-template/pull/1277) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor models to use cascade delete relationships . PR [#1276](https://github.com/tiangolo/full-stack-fastapi-template/pull/1276) by [@alejsdev](https://github.com/alejsdev). * 🔥 Remove `USERS_OPEN_REGISTRATION` config, make registration enabled by default. PR [#1274](https://github.com/tiangolo/full-stack-fastapi-template/pull/1274) by [@alejsdev](https://github.com/alejsdev). * 🔧 Reuse database url from config in alembic setup. PR [#1229](https://github.com/tiangolo/full-stack-fastapi-template/pull/1229) by [@patrick91](https://github.com/patrick91). From edc3d28b9c442aef02e4f59b9646883ce1fa503c Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:50:55 -0500 Subject: [PATCH 593/771] =?UTF-8?q?=F0=9F=93=9D=20Add=20End-to-End=20Testi?= =?UTF-8?q?ng=20with=20Playwright=20to=20frontend=20`README.md`=20(#1279)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- README.md | 1 + frontend/README.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/README.md b/README.md index d7e04da26c..b2dcf9aa41 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - 💃 Using TypeScript, hooks, Vite, and other parts of a modern frontend stack. - 🎨 [Chakra UI](https://chakra-ui.com) for the frontend components. - 🤖 An automatically generated frontend client. + - 🧪 [Playwright](https://playwright.dev) for End-to-End testing. - 🦇 Dark mode support. - 🐋 [Docker Compose](https://www.docker.com) for development and production. - 🔒 Secure password hashing by default. diff --git a/frontend/README.md b/frontend/README.md index 7dd453b6dc..13e3a8c488 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -115,3 +115,33 @@ The frontend code is structured as follows: * `frontend/src/hooks` - Custom hooks. * `frontend/src/routes` - The different routes of the frontend which include the pages. * `theme.tsx` - The Chakra UI custom theme. + +## End-to-End Testing with Playwright + +The frontend includes initial end-to-end tests using Playwright. To run the tests, you need to have the Docker Compose stack running. Start the stack with the following command: + +```bash +docker compose up -d +``` + +Then, you can run the tests with the following command: + +```bash +npx playwright test +``` + +You can also run your tests in UI mode to see the browser and interact with it running: + +```bash +npx playwright test --ui +``` + +To stop and remove the Docker Compose stack and clean the data created in tests, use the following command: + +```bash +docker compose down -v +``` + +To update the tests, navigate to the tests directory and modify the existing test files or add new ones as needed. + +For more information on writing and running Playwright tests, refer to the official [Playwright documentation](https://playwright.dev/docs/intro). \ No newline at end of file From 4335a7854c2aa3fd7e09015e93f6d355a96abea4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 1 Aug 2024 16:51:14 +0000 Subject: [PATCH 594/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 02d275b8df..f8a1d72888 100644 --- a/release-notes.md +++ b/release-notes.md @@ -92,6 +92,7 @@ ### Docs +* 📝 Add End-to-End Testing with Playwright to frontend `README.md`. PR [#1279](https://github.com/tiangolo/full-stack-fastapi-template/pull/1279) by [@alejsdev](https://github.com/alejsdev). * 📝 Update release-notes.md. PR [#1220](https://github.com/tiangolo/full-stack-fastapi-template/pull/1220) by [@alejsdev](https://github.com/alejsdev). * ✏️ Update `README.md`. PR [#1205](https://github.com/tiangolo/full-stack-fastapi-template/pull/1205) by [@Craz1k0ek](https://github.com/Craz1k0ek). * ✏️ Fix Adminer URL in `deployment.md`. PR [#1194](https://github.com/tiangolo/full-stack-fastapi-template/pull/1194) by [@PhilippWu](https://github.com/PhilippWu). From de599289d304763f66d933db2c578b5f123612ab Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:01:03 -0500 Subject: [PATCH 595/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Tweaks=20in=20fron?= =?UTF-8?q?tend=20(#1273)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Admin/AddUser.tsx | 5 +- frontend/src/components/Admin/EditUser.tsx | 5 +- frontend/src/components/Items/AddItem.tsx | 4 +- frontend/src/components/Items/EditItem.tsx | 4 +- .../UserSettings/ChangePassword.tsx | 5 +- .../UserSettings/DeleteConfirmation.tsx | 4 +- .../UserSettings/UserInformation.tsx | 13 +- frontend/src/hooks/useAuth.ts | 2 +- frontend/src/routes/_layout/admin.tsx | 190 +++++++++++------- frontend/src/routes/_layout/items.tsx | 32 +-- frontend/src/routes/recover-password.tsx | 5 +- frontend/src/routes/reset-password.tsx | 5 +- frontend/src/utils.ts | 11 + frontend/tests/auth.setup.ts | 1 - frontend/tests/config.ts | 22 +- 15 files changed, 184 insertions(+), 124 deletions(-) diff --git a/frontend/src/components/Admin/AddUser.tsx b/frontend/src/components/Admin/AddUser.tsx index 8ded725b31..a24a18a78e 100644 --- a/frontend/src/components/Admin/AddUser.tsx +++ b/frontend/src/components/Admin/AddUser.tsx @@ -20,7 +20,7 @@ import { type SubmitHandler, useForm } from "react-hook-form" import { type UserCreate, UsersService } from "../../client" import type { ApiError } from "../../client/core/ApiError" import useCustomToast from "../../hooks/useCustomToast" -import { emailPattern } from "../../utils" +import { emailPattern, handleError } from "../../utils" interface AddUserProps { isOpen: boolean @@ -62,8 +62,7 @@ const AddUser = ({ isOpen, onClose }: AddUserProps) => { onClose() }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["users"] }) diff --git a/frontend/src/components/Admin/EditUser.tsx b/frontend/src/components/Admin/EditUser.tsx index ffba135e9c..d7885ab174 100644 --- a/frontend/src/components/Admin/EditUser.tsx +++ b/frontend/src/components/Admin/EditUser.tsx @@ -24,7 +24,7 @@ import { UsersService, } from "../../client" import useCustomToast from "../../hooks/useCustomToast" -import { emailPattern } from "../../utils" +import { emailPattern, handleError } from "../../utils" interface EditUserProps { user: UserPublic @@ -60,8 +60,7 @@ const EditUser = ({ user, isOpen, onClose }: EditUserProps) => { onClose() }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["users"] }) diff --git a/frontend/src/components/Items/AddItem.tsx b/frontend/src/components/Items/AddItem.tsx index 21cc06d4e8..fa5682da3f 100644 --- a/frontend/src/components/Items/AddItem.tsx +++ b/frontend/src/components/Items/AddItem.tsx @@ -17,6 +17,7 @@ import { type SubmitHandler, useForm } from "react-hook-form" import { type ApiError, type ItemCreate, ItemsService } from "../../client" import useCustomToast from "../../hooks/useCustomToast" +import { handleError } from "../../utils" interface AddItemProps { isOpen: boolean @@ -49,8 +50,7 @@ const AddItem = ({ isOpen, onClose }: AddItemProps) => { onClose() }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["items"] }) diff --git a/frontend/src/components/Items/EditItem.tsx b/frontend/src/components/Items/EditItem.tsx index 6bbe79acef..3d40cdc03a 100644 --- a/frontend/src/components/Items/EditItem.tsx +++ b/frontend/src/components/Items/EditItem.tsx @@ -22,6 +22,7 @@ import { ItemsService, } from "../../client" import useCustomToast from "../../hooks/useCustomToast" +import { handleError } from "../../utils" interface EditItemProps { item: ItemPublic @@ -51,8 +52,7 @@ const EditItem = ({ item, isOpen, onClose }: EditItemProps) => { onClose() }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["items"] }) diff --git a/frontend/src/components/UserSettings/ChangePassword.tsx b/frontend/src/components/UserSettings/ChangePassword.tsx index 439ee80867..73217939fc 100644 --- a/frontend/src/components/UserSettings/ChangePassword.tsx +++ b/frontend/src/components/UserSettings/ChangePassword.tsx @@ -14,7 +14,7 @@ import { type SubmitHandler, useForm } from "react-hook-form" import { type ApiError, type UpdatePassword, UsersService } from "../../client" import useCustomToast from "../../hooks/useCustomToast" -import { confirmPasswordRules, passwordRules } from "../../utils" +import { confirmPasswordRules, handleError, passwordRules } from "../../utils" interface UpdatePasswordForm extends UpdatePassword { confirm_password: string @@ -42,8 +42,7 @@ const ChangePassword = () => { reset() }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, }) diff --git a/frontend/src/components/UserSettings/DeleteConfirmation.tsx b/frontend/src/components/UserSettings/DeleteConfirmation.tsx index 7301d6894d..5bbdcdd6c7 100644 --- a/frontend/src/components/UserSettings/DeleteConfirmation.tsx +++ b/frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -14,6 +14,7 @@ import { useForm } from "react-hook-form" import { type ApiError, UsersService } from "../../client" import useAuth from "../../hooks/useAuth" import useCustomToast from "../../hooks/useCustomToast" +import { handleError } from "../../utils" interface DeleteProps { isOpen: boolean @@ -42,8 +43,7 @@ const DeleteConfirmation = ({ isOpen, onClose }: DeleteProps) => { onClose() }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["currentUser"] }) diff --git a/frontend/src/components/UserSettings/UserInformation.tsx b/frontend/src/components/UserSettings/UserInformation.tsx index 03e2fdfb15..d066a846a6 100644 --- a/frontend/src/components/UserSettings/UserInformation.tsx +++ b/frontend/src/components/UserSettings/UserInformation.tsx @@ -23,7 +23,7 @@ import { } from "../../client" import useAuth from "../../hooks/useAuth" import useCustomToast from "../../hooks/useCustomToast" -import { emailPattern } from "../../utils" +import { emailPattern, handleError } from "../../utils" const UserInformation = () => { const queryClient = useQueryClient() @@ -57,13 +57,10 @@ const UserInformation = () => { showToast("Success!", "User updated successfully.", "success") }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, onSettled: () => { - // TODO: can we do just one call now? - queryClient.invalidateQueries({ queryKey: ["users"] }) - queryClient.invalidateQueries({ queryKey: ["currentUser"] }) + queryClient.invalidateQueries() }, }) @@ -104,6 +101,8 @@ const UserInformation = () => { size="md" py={2} color={!currentUser?.full_name ? "ui.dim" : "inherit"} + isTruncated + maxWidth="250px" > {currentUser?.full_name || "N/A"} @@ -125,7 +124,7 @@ const UserInformation = () => { w="auto" /> ) : ( - + {currentUser?.email} )} diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 20f09d1103..76b0abdfd3 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -47,7 +47,7 @@ const useAuth = () => { errDetail = err.message } - showToast("Something went wrong.", `${errDetail}`, "error") + showToast("Something went wrong.", errDetail, "error") }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["users"] }) diff --git a/frontend/src/routes/_layout/admin.tsx b/frontend/src/routes/_layout/admin.tsx index 9ecddadf93..644653ff79 100644 --- a/frontend/src/routes/_layout/admin.tsx +++ b/frontend/src/routes/_layout/admin.tsx @@ -1,6 +1,7 @@ import { Badge, Box, + Button, Container, Flex, Heading, @@ -13,90 +14,65 @@ import { Thead, Tr, } from "@chakra-ui/react" -import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query" -import { createFileRoute } from "@tanstack/react-router" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { createFileRoute, useNavigate } from "@tanstack/react-router" +import { useEffect } from "react" +import { z } from "zod" -import { Suspense } from "react" import { type UserPublic, UsersService } from "../../client" import AddUser from "../../components/Admin/AddUser" import ActionsMenu from "../../components/Common/ActionsMenu" import Navbar from "../../components/Common/Navbar" +const usersSearchSchema = z.object({ + page: z.number().catch(1), +}) + export const Route = createFileRoute("/_layout/admin")({ component: Admin, + validateSearch: (search) => usersSearchSchema.parse(search), }) -const MembersTableBody = () => { +const PER_PAGE = 5 + +function getUsersQueryOptions({ page }: { page: number }) { + return { + queryFn: () => + UsersService.readUsers({ skip: (page - 1) * PER_PAGE, limit: PER_PAGE }), + queryKey: ["users", { page }], + } +} + +function UsersTable() { const queryClient = useQueryClient() const currentUser = queryClient.getQueryData(["currentUser"]) + const { page } = Route.useSearch() + const navigate = useNavigate({ from: Route.fullPath }) + const setPage = (page: number) => + navigate({ search: (prev) => ({ ...prev, page }) }) - const { data: users } = useSuspenseQuery({ - queryKey: ["users"], - queryFn: () => UsersService.readUsers({}), + const { + data: users, + isPending, + isPlaceholderData, + } = useQuery({ + ...getUsersQueryOptions({ page }), + placeholderData: (prevData) => prevData, }) - return ( -
- {users.data.map((user) => ( - - - - - - - - ))} - - ) -} + const hasNextPage = !isPlaceholderData && users?.data.length === PER_PAGE + const hasPreviousPage = page > 1 -const MembersBodySkeleton = () => { - return ( - - - {new Array(5).fill(null).map((_, index) => ( - - ))} - - - ) -} + useEffect(() => { + if (hasNextPage) { + queryClient.prefetchQuery(getUsersQueryOptions({ page: page + 1 })) + } + }, [page, queryClient, hasNextPage]) -function Admin() { return ( - - - User Management - - + <> -
- {user.full_name || "N/A"} - {currentUser?.id === user.id && ( - - You - - )} - {user.email}{user.is_superuser ? "Superuser" : "User"} - - - {user.is_active ? "Active" : "Inactive"} - - - -
- -
+
@@ -106,11 +82,89 @@ function Admin() { - }> - - + {isPending ? ( + + + {new Array(4).fill(null).map((_, index) => ( + + ))} + + + ) : ( + + {users?.data.map((user) => ( + + + + + + + + ))} + + )}
Full nameActions
+ +
+ {user.full_name || "N/A"} + {currentUser?.id === user.id && ( + + You + + )} + + {user.email} + {user.is_superuser ? "Superuser" : "User"} + + + {user.is_active ? "Active" : "Inactive"} + + + +
+ + + Page {page} + + + + ) +} + +function Admin() { + return ( + + + Users Management + + + + ) } diff --git a/frontend/src/routes/_layout/items.tsx b/frontend/src/routes/_layout/items.tsx index 4b618c127e..174fa83c9b 100644 --- a/frontend/src/routes/_layout/items.tsx +++ b/frontend/src/routes/_layout/items.tsx @@ -3,7 +3,7 @@ import { Container, Flex, Heading, - Skeleton, + SkeletonText, Table, TableContainer, Tbody, @@ -64,7 +64,7 @@ function ItemsTable() { if (hasNextPage) { queryClient.prefetchQuery(getItemsQueryOptions({ page: page + 1 })) } - }, [page, queryClient]) + }, [page, queryClient, hasNextPage]) return ( <> @@ -80,25 +80,27 @@ function ItemsTable() { {isPending ? ( - {new Array(5).fill(null).map((_, index) => ( - - {new Array(4).fill(null).map((_, index) => ( - - - - - - ))} - - ))} + + {new Array(4).fill(null).map((_, index) => ( + + + + ))} + ) : ( {items?.data.map((item) => ( {item.id} - {item.title} - + + {item.title} + + {item.description || "N/A"} diff --git a/frontend/src/routes/recover-password.tsx b/frontend/src/routes/recover-password.tsx index 6ea24bf37d..5716728bbb 100644 --- a/frontend/src/routes/recover-password.tsx +++ b/frontend/src/routes/recover-password.tsx @@ -14,7 +14,7 @@ import { type SubmitHandler, useForm } from "react-hook-form" import { type ApiError, LoginService } from "../client" import { isLoggedIn } from "../hooks/useAuth" import useCustomToast from "../hooks/useCustomToast" -import { emailPattern } from "../utils" +import { emailPattern, handleError } from "../utils" interface FormData { email: string @@ -57,8 +57,7 @@ function RecoverPassword() { reset() }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, }) diff --git a/frontend/src/routes/reset-password.tsx b/frontend/src/routes/reset-password.tsx index 11bc552d9b..f5ee763a3e 100644 --- a/frontend/src/routes/reset-password.tsx +++ b/frontend/src/routes/reset-password.tsx @@ -15,7 +15,7 @@ import { type SubmitHandler, useForm } from "react-hook-form" import { type ApiError, LoginService, type NewPassword } from "../client" import { isLoggedIn } from "../hooks/useAuth" import useCustomToast from "../hooks/useCustomToast" -import { confirmPasswordRules, passwordRules } from "../utils" +import { confirmPasswordRules, handleError, passwordRules } from "../utils" interface NewPasswordForm extends NewPassword { confirm_password: string @@ -65,8 +65,7 @@ function ResetPassword() { navigate({ to: "/login" }) }, onError: (err: ApiError) => { - const errDetail = (err.body as any)?.detail - showToast("Something went wrong.", `${errDetail}`, "error") + handleError(err, showToast) }, }) diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index 3d504b3b9a..99f906303c 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -1,3 +1,5 @@ +import type { ApiError } from "./client" + export const emailPattern = { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: "Invalid email address", @@ -40,3 +42,12 @@ export const confirmPasswordRules = ( return rules } + +export const handleError = (err: ApiError, showToast: any) => { + const errDetail = (err.body as any)?.detail + let errorMessage = errDetail || "Something went wrong." + if (Array.isArray(errDetail) && errDetail.length > 0) { + errorMessage = errDetail[0].msg + } + showToast("Error", errorMessage, "error") +} diff --git a/frontend/tests/auth.setup.ts b/frontend/tests/auth.setup.ts index d4e196e31e..3882f4f810 100644 --- a/frontend/tests/auth.setup.ts +++ b/frontend/tests/auth.setup.ts @@ -3,7 +3,6 @@ import { firstSuperuser, firstSuperuserPassword } from "./config.ts" const authFile = "playwright/.auth/user.json" - setup("authenticate", async ({ page }) => { await page.goto("/login") await page.getByPlaceholder("Email").fill(firstSuperuser) diff --git a/frontend/tests/config.ts b/frontend/tests/config.ts index d2265bbf8f..188cb367e3 100644 --- a/frontend/tests/config.ts +++ b/frontend/tests/config.ts @@ -1,21 +1,21 @@ -import dotenv from 'dotenv'; -import path from 'path'; -import { fileURLToPath } from 'url'; +import path from "node:path" +import { fileURLToPath } from "node:url" +import dotenv from "dotenv" -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) -dotenv.config({ path: path.join(__dirname, '../../.env') }); +dotenv.config({ path: path.join(__dirname, "../../.env") }) -const { FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD } = process.env; +const { FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD } = process.env if (typeof FIRST_SUPERUSER !== "string") { - throw new Error("Environment variable FIRST_SUPERUSER is undefined"); + throw new Error("Environment variable FIRST_SUPERUSER is undefined") } if (typeof FIRST_SUPERUSER_PASSWORD !== "string") { - throw new Error("Environment variable FIRST_SUPERUSER_PASSWORD is undefined"); + throw new Error("Environment variable FIRST_SUPERUSER_PASSWORD is undefined") } -export const firstSuperuser = FIRST_SUPERUSER as string; -export const firstSuperuserPassword = FIRST_SUPERUSER_PASSWORD as string; +export const firstSuperuser = FIRST_SUPERUSER as string +export const firstSuperuserPassword = FIRST_SUPERUSER_PASSWORD as string From 92f183ed510aae258b7154315eb4dc9259e407ca Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 1 Aug 2024 18:01:21 +0000 Subject: [PATCH 596/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index f8a1d72888..e69754e96d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -27,6 +27,7 @@ ### Refactors +* ♻️ Tweaks in frontend. PR [#1273](https://github.com/tiangolo/full-stack-fastapi-template/pull/1273) by [@alejsdev](https://github.com/alejsdev). * ♻️ Add random password util and refactor tests. PR [#1277](https://github.com/tiangolo/full-stack-fastapi-template/pull/1277) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor models to use cascade delete relationships . PR [#1276](https://github.com/tiangolo/full-stack-fastapi-template/pull/1276) by [@alejsdev](https://github.com/alejsdev). * 🔥 Remove `USERS_OPEN_REGISTRATION` config, make registration enabled by default. PR [#1274](https://github.com/tiangolo/full-stack-fastapi-template/pull/1274) by [@alejsdev](https://github.com/alejsdev). From 20c7932d171c9e5ec36ca2c224978c3d6b027c08 Mon Sep 17 00:00:00 2001 From: Abdul Date: Thu, 1 Aug 2024 11:48:00 -0700 Subject: [PATCH 597/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Regenerate=20clien?= =?UTF-8?q?t=20to=20use=20UUID=20instead=20of=20id=20integers=20and=20upda?= =?UTF-8?q?te=20frontend=20(#1281)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/client/core/request.ts | 6 +- frontend/src/client/models.ts | 6 +- frontend/src/client/schemas.ts | 45 +++++++++++++- frontend/src/client/services.ts | 60 +++++++++---------- .../src/components/Common/DeleteAlert.tsx | 4 +- 5 files changed, 80 insertions(+), 41 deletions(-) diff --git a/frontend/src/client/core/request.ts b/frontend/src/client/core/request.ts index 99d38b46f1..6abb0e8f41 100644 --- a/frontend/src/client/core/request.ts +++ b/frontend/src/client/core/request.ts @@ -1,9 +1,9 @@ import axios from "axios" import type { AxiosError, + AxiosInstance, AxiosRequestConfig, AxiosResponse, - AxiosInstance, } from "axios" import { ApiError } from "./ApiError" @@ -151,12 +151,12 @@ export const getHeaders = async ( ) if (isStringWithValue(token)) { - headers["Authorization"] = `Bearer ${token}` + headers.Authorization = `Bearer ${token}` } if (isStringWithValue(username) && isStringWithValue(password)) { const credentials = base64(`${username}:${password}`) - headers["Authorization"] = `Basic ${credentials}` + headers.Authorization = `Basic ${credentials}` } if (options.body !== undefined) { diff --git a/frontend/src/client/models.ts b/frontend/src/client/models.ts index a8e1b7ab28..2c8074ddd6 100644 --- a/frontend/src/client/models.ts +++ b/frontend/src/client/models.ts @@ -19,8 +19,8 @@ export type ItemCreate = { export type ItemPublic = { title: string description?: string | null - id: number - owner_id: number + id: string + owner_id: string } export type ItemUpdate = { @@ -65,7 +65,7 @@ export type UserPublic = { is_active?: boolean is_superuser?: boolean full_name?: string | null - id: number + id: string } export type UserRegister = { diff --git a/frontend/src/client/schemas.ts b/frontend/src/client/schemas.ts index 0043c1ba13..9e92efd106 100644 --- a/frontend/src/client/schemas.ts +++ b/frontend/src/client/schemas.ts @@ -65,12 +65,15 @@ export const $ItemCreate = { title: { type: "string", isRequired: true, + maxLength: 255, + minLength: 1, }, description: { type: "any-of", contains: [ { type: "string", + maxLength: 255, }, { type: "null", @@ -85,12 +88,15 @@ export const $ItemPublic = { title: { type: "string", isRequired: true, + maxLength: 255, + minLength: 1, }, description: { type: "any-of", contains: [ { type: "string", + maxLength: 255, }, { type: "null", @@ -98,12 +104,14 @@ export const $ItemPublic = { ], }, id: { - type: "number", + type: "string", isRequired: true, + format: "uuid", }, owner_id: { - type: "number", + type: "string", isRequired: true, + format: "uuid", }, }, } as const @@ -115,6 +123,8 @@ export const $ItemUpdate = { contains: [ { type: "string", + maxLength: 255, + minLength: 1, }, { type: "null", @@ -126,6 +136,7 @@ export const $ItemUpdate = { contains: [ { type: "string", + maxLength: 255, }, { type: "null", @@ -169,6 +180,8 @@ export const $NewPassword = { new_password: { type: "string", isRequired: true, + maxLength: 40, + minLength: 8, }, }, } as const @@ -191,10 +204,14 @@ export const $UpdatePassword = { current_password: { type: "string", isRequired: true, + maxLength: 40, + minLength: 8, }, new_password: { type: "string", isRequired: true, + maxLength: 40, + minLength: 8, }, }, } as const @@ -204,6 +221,8 @@ export const $UserCreate = { email: { type: "string", isRequired: true, + format: "email", + maxLength: 255, }, is_active: { type: "boolean", @@ -218,6 +237,7 @@ export const $UserCreate = { contains: [ { type: "string", + maxLength: 255, }, { type: "null", @@ -227,6 +247,8 @@ export const $UserCreate = { password: { type: "string", isRequired: true, + maxLength: 40, + minLength: 8, }, }, } as const @@ -236,6 +258,8 @@ export const $UserPublic = { email: { type: "string", isRequired: true, + format: "email", + maxLength: 255, }, is_active: { type: "boolean", @@ -250,6 +274,7 @@ export const $UserPublic = { contains: [ { type: "string", + maxLength: 255, }, { type: "null", @@ -257,8 +282,9 @@ export const $UserPublic = { ], }, id: { - type: "number", + type: "string", isRequired: true, + format: "uuid", }, }, } as const @@ -268,16 +294,21 @@ export const $UserRegister = { email: { type: "string", isRequired: true, + format: "email", + maxLength: 255, }, password: { type: "string", isRequired: true, + maxLength: 40, + minLength: 8, }, full_name: { type: "any-of", contains: [ { type: "string", + maxLength: 255, }, { type: "null", @@ -294,6 +325,8 @@ export const $UserUpdate = { contains: [ { type: "string", + format: "email", + maxLength: 255, }, { type: "null", @@ -313,6 +346,7 @@ export const $UserUpdate = { contains: [ { type: "string", + maxLength: 255, }, { type: "null", @@ -324,6 +358,8 @@ export const $UserUpdate = { contains: [ { type: "string", + maxLength: 40, + minLength: 8, }, { type: "null", @@ -340,6 +376,7 @@ export const $UserUpdateMe = { contains: [ { type: "string", + maxLength: 255, }, { type: "null", @@ -351,6 +388,8 @@ export const $UserUpdateMe = { contains: [ { type: "string", + format: "email", + maxLength: 255, }, { type: "null", diff --git a/frontend/src/client/services.ts b/frontend/src/client/services.ts index e4b8138f1a..be024e4d11 100644 --- a/frontend/src/client/services.ts +++ b/frontend/src/client/services.ts @@ -4,20 +4,20 @@ import { request as __request } from "./core/request" import type { Body_login_login_access_token, + ItemCreate, + ItemPublic, + ItemUpdate, + ItemsPublic, Message, NewPassword, Token, - UserPublic, UpdatePassword, UserCreate, + UserPublic, UserRegister, - UsersPublic, UserUpdate, UserUpdateMe, - ItemCreate, - ItemPublic, - ItemsPublic, - ItemUpdate, + UsersPublic, } from "./models" export type TDataLoginAccessToken = { @@ -50,7 +50,7 @@ export class LoginService { formData: formData, mediaType: "application/x-www-form-urlencoded", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -85,7 +85,7 @@ export class LoginService { email, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -106,7 +106,7 @@ export class LoginService { body: requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -128,7 +128,7 @@ export class LoginService { email, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -151,14 +151,14 @@ export type TDataRegisterUser = { requestBody: UserRegister } export type TDataReadUserById = { - userId: number + userId: string } export type TDataUpdateUser = { requestBody: UserUpdate - userId: number + userId: string } export type TDataDeleteUser = { - userId: number + userId: string } export class UsersService { @@ -180,7 +180,7 @@ export class UsersService { limit, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -201,7 +201,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -248,7 +248,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -269,7 +269,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -290,7 +290,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -312,7 +312,7 @@ export class UsersService { user_id: userId, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -336,7 +336,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -356,7 +356,7 @@ export class UsersService { user_id: userId, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -382,7 +382,7 @@ export class UtilsService { email_to: emailTo, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -396,14 +396,14 @@ export type TDataCreateItem = { requestBody: ItemCreate } export type TDataReadItem = { - id: number + id: string } export type TDataUpdateItem = { - id: number + id: string requestBody: ItemUpdate } export type TDataDeleteItem = { - id: number + id: string } export class ItemsService { @@ -425,7 +425,7 @@ export class ItemsService { limit, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -446,7 +446,7 @@ export class ItemsService { body: requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -466,7 +466,7 @@ export class ItemsService { id, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -490,7 +490,7 @@ export class ItemsService { body: requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -510,7 +510,7 @@ export class ItemsService { id, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } diff --git a/frontend/src/components/Common/DeleteAlert.tsx b/frontend/src/components/Common/DeleteAlert.tsx index c89d1412ff..1528fc5fe1 100644 --- a/frontend/src/components/Common/DeleteAlert.tsx +++ b/frontend/src/components/Common/DeleteAlert.tsx @@ -16,7 +16,7 @@ import useCustomToast from "../../hooks/useCustomToast" interface DeleteProps { type: string - id: number + id: string isOpen: boolean onClose: () => void } @@ -30,7 +30,7 @@ const Delete = ({ type, id, isOpen, onClose }: DeleteProps) => { formState: { isSubmitting }, } = useForm() - const deleteEntity = async (id: number) => { + const deleteEntity = async (id: string) => { if (type === "Item") { await ItemsService.deleteItem({ id: id }) } else if (type === "User") { From 78f3c6e4b88ae77883874a02357213fe0cb09afe Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 1 Aug 2024 18:48:17 +0000 Subject: [PATCH 598/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index e69754e96d..e1a786f9e1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -27,6 +27,7 @@ ### Refactors +* ♻️ Regenerate client to use UUID instead of id integers and update frontend. PR [#1281](https://github.com/tiangolo/full-stack-fastapi-template/pull/1281) by [@rehanabdul](https://github.com/rehanabdul). * ♻️ Tweaks in frontend. PR [#1273](https://github.com/tiangolo/full-stack-fastapi-template/pull/1273) by [@alejsdev](https://github.com/alejsdev). * ♻️ Add random password util and refactor tests. PR [#1277](https://github.com/tiangolo/full-stack-fastapi-template/pull/1277) by [@alejsdev](https://github.com/alejsdev). * ♻️ Refactor models to use cascade delete relationships . PR [#1276](https://github.com/tiangolo/full-stack-fastapi-template/pull/1276) by [@alejsdev](https://github.com/alejsdev). From 5cde7f261780ac43fe8cd4e055d74f48cbd3f6cb Mon Sep 17 00:00:00 2001 From: Elijah Rippeth Date: Fri, 2 Aug 2024 11:47:06 -0400 Subject: [PATCH 599/771] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20Docker=20build=20w?= =?UTF-8?q?arning=20(#1283)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 126ed9f63d..8728c7b029 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,5 @@ # Stage 0, "build-stage", based on Node.js, to build and compile the frontend -FROM node:20 as build-stage +FROM node:20 AS build-stage WORKDIR /app From 5f4af6b9839f038fcf069ba2191a174589b7636d Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 2 Aug 2024 15:47:23 +0000 Subject: [PATCH 600/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index e1a786f9e1..6a914d225e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -27,6 +27,7 @@ ### Refactors +* 🚨 Fix Docker build warning. PR [#1283](https://github.com/tiangolo/full-stack-fastapi-template/pull/1283) by [@erip](https://github.com/erip). * ♻️ Regenerate client to use UUID instead of id integers and update frontend. PR [#1281](https://github.com/tiangolo/full-stack-fastapi-template/pull/1281) by [@rehanabdul](https://github.com/rehanabdul). * ♻️ Tweaks in frontend. PR [#1273](https://github.com/tiangolo/full-stack-fastapi-template/pull/1273) by [@alejsdev](https://github.com/alejsdev). * ♻️ Add random password util and refactor tests. PR [#1277](https://github.com/tiangolo/full-stack-fastapi-template/pull/1277) by [@alejsdev](https://github.com/alejsdev). From aa2bdf9972982454b8de7e8c9abf7541b5e9a7f6 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:04:54 -0500 Subject: [PATCH 601/771] =?UTF-8?q?=F0=9F=94=A7=20Update=20deploy=20workfl?= =?UTF-8?q?ows=20to=20exclude=20the=20main=20repository=20(#1284)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-production.yml | 2 ++ .github/workflows/deploy-staging.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 3625726906..37593ad15a 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -7,6 +7,8 @@ on: jobs: deploy: + # Do not deploy in the main repository, only in user projects + if: github.repository_owner != 'tiangolo' runs-on: - self-hosted - production diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index dd7bf83c27..f455836645 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -7,6 +7,8 @@ on: jobs: deploy: + # Do not deploy in the main repository, only in user projects + if: github.repository_owner != 'tiangolo' runs-on: - self-hosted - staging From e13be8f314427b43399e50de8d093770d45e9b60 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 2 Aug 2024 19:05:10 +0000 Subject: [PATCH 602/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 6a914d225e..8d06d3488a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -112,6 +112,7 @@ ### Internal +* 🔧 Update deploy workflows to exclude the main repository. PR [#1284](https://github.com/tiangolo/full-stack-fastapi-template/pull/1284) by [@alejsdev](https://github.com/alejsdev). * 👷 Update issue-manager.yml GitHub Action permissions. PR [#1278](https://github.com/tiangolo/full-stack-fastapi-template/pull/1278) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Bump setuptools from 69.1.1 to 70.0.0 in /backend. PR [#1255](https://github.com/tiangolo/full-stack-fastapi-template/pull/1255) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆️ Bump certifi from 2024.2.2 to 2024.7.4 in /backend. PR [#1250](https://github.com/tiangolo/full-stack-fastapi-template/pull/1250) by [@dependabot[bot]](https://github.com/apps/dependabot). From 75a902546c1d8d7e71daa250d6cafc6f051ec3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 2 Aug 2024 14:34:32 -0500 Subject: [PATCH 603/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20links=20from=20?= =?UTF-8?q?tiangolo=20repo=20to=20fastapi=20org=20repo=20(#1285)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/config.yml | 4 ++-- .github/workflows/deploy-production.yml | 2 +- .github/workflows/deploy-staging.yml | 2 +- README.md | 32 ++++++++++++------------- backend/app/core/db.py | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index fe71863122..50bde36072 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,7 +4,7 @@ contact_links: about: Please report security vulnerabilities to security@tiangolo.com - name: Question or Problem about: Ask a question or ask about a problem in GitHub Discussions. - url: https://github.com/tiangolo/full-stack-fastapi-template/discussions/categories/questions + url: https://github.com/fastapi/full-stack-fastapi-template/discussions/categories/questions - name: Feature Request about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already. - url: https://github.com/tiangolo/full-stack-fastapi-template/discussions/categories/questions + url: https://github.com/fastapi/full-stack-fastapi-template/discussions/categories/questions diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 37593ad15a..a64d02a156 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -8,7 +8,7 @@ on: jobs: deploy: # Do not deploy in the main repository, only in user projects - if: github.repository_owner != 'tiangolo' + if: github.repository_owner != 'fastapi' runs-on: - self-hosted - production diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index f455836645..26bd692fd8 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -8,7 +8,7 @@ on: jobs: deploy: # Do not deploy in the main repository, only in user projects - if: github.repository_owner != 'tiangolo' + if: github.repository_owner != 'fastapi' runs-on: - self-hosted - staging diff --git a/README.md b/README.md index b2dcf9aa41..afe124f3fb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Full Stack FastAPI Template -Test -Coverage +Test +Coverage ## Technology Stack and Features @@ -26,31 +26,31 @@ ### Dashboard Login -[![API docs](img/login.png)](https://github.com/tiangolo/full-stack-fastapi-template) +[![API docs](img/login.png)](https://github.com/fastapi/full-stack-fastapi-template) ### Dashboard - Admin -[![API docs](img/dashboard.png)](https://github.com/tiangolo/full-stack-fastapi-template) +[![API docs](img/dashboard.png)](https://github.com/fastapi/full-stack-fastapi-template) ### Dashboard - Create User -[![API docs](img/dashboard-create.png)](https://github.com/tiangolo/full-stack-fastapi-template) +[![API docs](img/dashboard-create.png)](https://github.com/fastapi/full-stack-fastapi-template) ### Dashboard - Items -[![API docs](img/dashboard-items.png)](https://github.com/tiangolo/full-stack-fastapi-template) +[![API docs](img/dashboard-items.png)](https://github.com/fastapi/full-stack-fastapi-template) ### Dashboard - User Settings -[![API docs](img/dashboard-user-settings.png)](https://github.com/tiangolo/full-stack-fastapi-template) +[![API docs](img/dashboard-user-settings.png)](https://github.com/fastapi/full-stack-fastapi-template) ### Dashboard - Dark Mode -[![API docs](img/dashboard-dark.png)](https://github.com/tiangolo/full-stack-fastapi-template) +[![API docs](img/dashboard-dark.png)](https://github.com/fastapi/full-stack-fastapi-template) ### Interactive API Documentation -[![API docs](img/docs.png)](https://github.com/tiangolo/full-stack-fastapi-template) +[![API docs](img/docs.png)](https://github.com/fastapi/full-stack-fastapi-template) ## How To Use It @@ -68,7 +68,7 @@ But you can do the following: - Clone this repository manually, set the name with the name of the project you want to use, for example `my-full-stack`: ```bash -git clone git@github.com:tiangolo/full-stack-fastapi-template.git my-full-stack +git clone git@github.com:fastapi/full-stack-fastapi-template.git my-full-stack ``` - Enter into the new directory: @@ -86,7 +86,7 @@ git remote set-url origin git@github.com:octocat/my-full-stack.git - Add this repo as another "remote" to allow you to get updates later: ```bash -git remote add upstream git@github.com:tiangolo/full-stack-fastapi-template.git +git remote add upstream git@github.com:fastapi/full-stack-fastapi-template.git ``` - Push the code to your new repository: @@ -106,8 +106,8 @@ git remote -v origin git@github.com:octocat/my-full-stack.git (fetch) origin git@github.com:octocat/my-full-stack.git (push) -upstream git@github.com:tiangolo/full-stack-fastapi-template.git (fetch) -upstream git@github.com:tiangolo/full-stack-fastapi-template.git (push) +upstream git@github.com:fastapi/full-stack-fastapi-template.git (fetch) +upstream git@github.com:fastapi/full-stack-fastapi-template.git (push) ``` - Pull the latest changes without merging: @@ -181,16 +181,16 @@ Decide a name for your new project's directory, you will use it below. For examp Go to the directory that will be the parent of your project, and run the command with your project's name: ```bash -copier copy https://github.com/tiangolo/full-stack-fastapi-template my-awesome-project --trust +copier copy https://github.com/fastapi/full-stack-fastapi-template my-awesome-project --trust ``` If you have `pipx` and you didn't install `copier`, you can run it directly: ```bash -pipx run copier copy https://github.com/tiangolo/full-stack-fastapi-template my-awesome-project --trust +pipx run copier copy https://github.com/fastapi/full-stack-fastapi-template my-awesome-project --trust ``` -**Note** the `--trust` option is necessary to be able to execute a [post-creation script](https://github.com/tiangolo/full-stack-fastapi-template/blob/master/.copier/update_dotenv.py) that updates your `.env` files. +**Note** the `--trust` option is necessary to be able to execute a [post-creation script](https://github.com/fastapi/full-stack-fastapi-template/blob/master/.copier/update_dotenv.py) that updates your `.env` files. ### Input Variables diff --git a/backend/app/core/db.py b/backend/app/core/db.py index 782b79d8a4..d260a856d2 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -9,7 +9,7 @@ # make sure all SQLModel models are imported (app.models) before initializing DB # otherwise, SQLModel might fail to initialize relationships properly -# for more details: https://github.com/tiangolo/full-stack-fastapi-template/issues/28 +# for more details: https://github.com/fastapi/full-stack-fastapi-template/issues/28 def init_db(session: Session) -> None: From 2c784d8f382b9f899af0e8b6a3bd2eadacd4adc1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 2 Aug 2024 19:37:15 +0000 Subject: [PATCH 604/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 8d06d3488a..6d699f5d81 100644 --- a/release-notes.md +++ b/release-notes.md @@ -95,6 +95,7 @@ ### Docs +* 📝 Update links from tiangolo repo to fastapi org repo. PR [#1285](https://github.com/fastapi/full-stack-fastapi-template/pull/1285) by [@tiangolo](https://github.com/tiangolo). * 📝 Add End-to-End Testing with Playwright to frontend `README.md`. PR [#1279](https://github.com/tiangolo/full-stack-fastapi-template/pull/1279) by [@alejsdev](https://github.com/alejsdev). * 📝 Update release-notes.md. PR [#1220](https://github.com/tiangolo/full-stack-fastapi-template/pull/1220) by [@alejsdev](https://github.com/alejsdev). * ✏️ Update `README.md`. PR [#1205](https://github.com/tiangolo/full-stack-fastapi-template/pull/1205) by [@Craz1k0ek](https://github.com/Craz1k0ek). From 64c07704821e64fdedbd4966facf8b8f4a723d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 2 Aug 2024 14:52:08 -0500 Subject: [PATCH 605/771] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.7.?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/release-notes.md b/release-notes.md index 6d699f5d81..33a18e2a15 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,11 +2,24 @@ ## Latest Changes +## 0.7.0 + +Lots of new things! 🎁 + +* E2E tests with Playwright. +* Mailcatcher configuration, to develop and test email handling. +* Pagination. +* UUIDs for database keys. +* New user sign up. +* Support for deploying to multiple environments (staging, prod). +* Many refactors and improvements. +* Several dependency upgrades. + ### Features -* ✨ Add User Settings e2e tests . PR [#1271](https://github.com/tiangolo/full-stack-fastapi-template/pull/1271) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add User Settings e2e tests. PR [#1271](https://github.com/tiangolo/full-stack-fastapi-template/pull/1271) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Reset Password e2e tests. PR [#1270](https://github.com/tiangolo/full-stack-fastapi-template/pull/1270) by [@alejsdev](https://github.com/alejsdev). -* ✨ Add Sign Up e2e tests . PR [#1268](https://github.com/tiangolo/full-stack-fastapi-template/pull/1268) by [@alejsdev](https://github.com/alejsdev). +* ✨ Add Sign Up e2e tests. PR [#1268](https://github.com/tiangolo/full-stack-fastapi-template/pull/1268) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Sign Up and make `OPEN_USER_REGISTRATION=True` by default. PR [#1265](https://github.com/tiangolo/full-stack-fastapi-template/pull/1265) by [@alejsdev](https://github.com/alejsdev). * ✨ Add Login e2e tests. PR [#1264](https://github.com/tiangolo/full-stack-fastapi-template/pull/1264) by [@alejsdev](https://github.com/alejsdev). * ✨ Add initial setup for frontend / end-to-end tests with Playwright. PR [#1261](https://github.com/tiangolo/full-stack-fastapi-template/pull/1261) by [@alejsdev](https://github.com/alejsdev). From 72e5b4f7abccb148687b37ef4024ed9d128220bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 3 Aug 2024 14:23:42 -0500 Subject: [PATCH 606/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20issue-manager?= =?UTF-8?q?=20(#1288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/issue-manager.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index e491dce6f2..f172dfb6a4 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -2,30 +2,41 @@ name: Issue Manager on: schedule: - - cron: "0 0 * * *" + - cron: "13 4 * * *" issue_comment: types: - created - - edited issues: types: - labeled + pull_request_target: + types: + - labeled + workflow_dispatch: permissions: issues: write jobs: issue-manager: + if: github.repository_owner == 'fastapi' runs-on: ubuntu-latest steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" - uses: tiangolo/issue-manager@0.5.0 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { - "users": ["tiangolo"], "delay": 864000, - "message": "Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues." + "message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs." + }, + "changes-requested": { + "delay": 2628000, + "message": "As this PR had requested changes to be applied but has been inactive for a while, it's now going to be closed. But if there's anyone interested, feel free to create a new PR." } } From 8f2f9c65985119c7fb972330d5f6ea32f976d828 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 3 Aug 2024 19:24:01 +0000 Subject: [PATCH 607/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 33a18e2a15..c187c611d0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Internal + +* 👷 Update issue-manager. PR [#1288](https://github.com/fastapi/full-stack-fastapi-template/pull/1288) by [@tiangolo](https://github.com/tiangolo). + ## 0.7.0 Lots of new things! 🎁 From f07bc4bca6b3006b7155ed29bcea5ff7c2b6e70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 12 Aug 2024 21:11:24 -0500 Subject: [PATCH 608/771] =?UTF-8?q?=F0=9F=91=B7=20Add=20GitHub=20Action=20?= =?UTF-8?q?add-to-project=20(#1297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/add-to-project.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/add-to-project.yml diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml new file mode 100644 index 0000000000..dccea83f35 --- /dev/null +++ b/.github/workflows/add-to-project.yml @@ -0,0 +1,18 @@ +name: Add to Project + +on: + pull_request_target: + issues: + types: + - opened + - reopened + +jobs: + add-to-project: + name: Add to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/fastapi/projects/2 + github-token: ${{ secrets.PROJECTS_TOKEN }} From d31ee0f58702762739e68fc27e5b0dd8283596e7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 13 Aug 2024 02:11:43 +0000 Subject: [PATCH 609/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index c187c611d0..1e511099bd 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Internal +* 👷 Add GitHub Action add-to-project. PR [#1297](https://github.com/fastapi/full-stack-fastapi-template/pull/1297) by [@tiangolo](https://github.com/tiangolo). * 👷 Update issue-manager. PR [#1288](https://github.com/fastapi/full-stack-fastapi-template/pull/1288) by [@tiangolo](https://github.com/tiangolo). ## 0.7.0 From 9c56a96ddb125be93b461e1e16f8f003af85f364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 12 Aug 2024 21:44:18 -0500 Subject: [PATCH 610/771] =?UTF-8?q?=F0=9F=91=B7=20Add=20GitHub=20Action=20?= =?UTF-8?q?labeler=20(#1298)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 12 ++++++++++++ .github/workflows/labeler.yml | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..e19f33b788 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,12 @@ +docs: + - changed-files: + - any-glob-to-any-file: + - '**/*.md' + +internal: + - changed-files: + - any-glob-to-any-file: + - .github/**/* + - scripts/**/* + - .gitignore + - .pre-commit-config.yaml diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000000..9cbdfda213 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,12 @@ +name: Pull Request Labeler +on: + pull_request_target: + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 From ebab88daed728a5096458b775af6430b8eaee5ae Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 13 Aug 2024 02:45:00 +0000 Subject: [PATCH 611/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 1e511099bd..2cc0f97154 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Internal +* 👷 Add GitHub Action labeler. PR [#1298](https://github.com/fastapi/full-stack-fastapi-template/pull/1298) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action add-to-project. PR [#1297](https://github.com/fastapi/full-stack-fastapi-template/pull/1297) by [@tiangolo](https://github.com/tiangolo). * 👷 Update issue-manager. PR [#1288](https://github.com/fastapi/full-stack-fastapi-template/pull/1288) by [@tiangolo](https://github.com/tiangolo). From 3365d7e41e2cb99c05474d906cc6154037e634e5 Mon Sep 17 00:00:00 2001 From: jjaakko <2248434+jjaakko@users.noreply.github.com> Date: Tue, 13 Aug 2024 06:58:31 +0300 Subject: [PATCH 612/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20duplicate?= =?UTF-8?q?d=20information=20in=20the=20ItemCreate=20model=20(#1287)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/models.py b/backend/app/models.py index 3472327658..90ef5559e3 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -64,7 +64,7 @@ class ItemBase(SQLModel): # Properties to receive on item creation class ItemCreate(ItemBase): - title: str = Field(min_length=1, max_length=255) + pass # Properties to receive on item update From c3ec86883ae8d5cb53829ee7a1aa1eb0c2087a30 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 13 Aug 2024 03:58:49 +0000 Subject: [PATCH 613/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 2cc0f97154..a808cc76bc 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Refactors + +* ♻️ Remove duplicate information in the ItemCreate model. PR [#1287](https://github.com/fastapi/full-stack-fastapi-template/pull/1287) by [@jjaakko](https://github.com/jjaakko). + ### Internal * 👷 Add GitHub Action labeler. PR [#1298](https://github.com/fastapi/full-stack-fastapi-template/pull/1298) by [@tiangolo](https://github.com/tiangolo). From 833e2c0093d58fb2befb0abc6d721f55d2d92400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 13 Aug 2024 00:35:21 -0500 Subject: [PATCH 614/771] =?UTF-8?q?=F0=9F=91=B7=20Add=20GitHub=20Action=20?= =?UTF-8?q?label-checker=20(#1299)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/labeler.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 9cbdfda213..7cb88936be 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,6 +1,13 @@ -name: Pull Request Labeler +name: Pull Request Labeler and Checker on: pull_request_target: + types: + - opened + - synchronize + - reopened + # For label-checker + - labeled + - unlabeled jobs: labeler: @@ -10,3 +17,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 + # Run this after labeler applied labels + check-labels: + name: Check labels + runs-on: ubuntu-latest + steps: + - uses: docker://agilepathway/pull-request-label-checker:latest + with: + one_of: breaking,security,feature,bug,refactor,upgrade,docs,lang-all,internal + repo_token: ${{ secrets.GITHUB_TOKEN }} From 33f23bc35d56a4f61ce78cb9a23fccaca269999c Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 13 Aug 2024 05:36:01 +0000 Subject: [PATCH 615/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index a808cc76bc..fb07e29d02 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ ### Internal +* 👷 Add GitHub Action label-checker. PR [#1299](https://github.com/fastapi/full-stack-fastapi-template/pull/1299) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action labeler. PR [#1298](https://github.com/fastapi/full-stack-fastapi-template/pull/1298) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action add-to-project. PR [#1297](https://github.com/fastapi/full-stack-fastapi-template/pull/1297) by [@tiangolo](https://github.com/tiangolo). * 👷 Update issue-manager. PR [#1288](https://github.com/fastapi/full-stack-fastapi-template/pull/1288) by [@tiangolo](https://github.com/tiangolo). From f51c0a93da43ce3332cb4616993b28f3627e80f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 13 Aug 2024 17:34:30 -0500 Subject: [PATCH 616/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20GitHub=20Action?= =?UTF-8?q?=20labeler=20permissions=20(#1300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/labeler.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 7cb88936be..c5c63a7be0 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -19,6 +19,8 @@ jobs: - uses: actions/labeler@v5 # Run this after labeler applied labels check-labels: + permissions: + pull-requests: read name: Check labels runs-on: ubuntu-latest steps: From 9dccb2e7e0834b46abf965a32b276752f82483dc Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 13 Aug 2024 22:34:48 +0000 Subject: [PATCH 617/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index fb07e29d02..34c343ce77 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ ### Internal +* 👷 Update GitHub Action labeler permissions. PR [#1300](https://github.com/fastapi/full-stack-fastapi-template/pull/1300) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action label-checker. PR [#1299](https://github.com/fastapi/full-stack-fastapi-template/pull/1299) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action labeler. PR [#1298](https://github.com/fastapi/full-stack-fastapi-template/pull/1298) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action add-to-project. PR [#1297](https://github.com/fastapi/full-stack-fastapi-template/pull/1297) by [@tiangolo](https://github.com/tiangolo). From 17080fc3982a25ebee70b00f900a753dad163351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 13 Aug 2024 22:57:33 -0500 Subject: [PATCH 618/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20GitHub=20Action?= =?UTF-8?q?=20labeler=20dependencies=20(#1302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/labeler.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index c5c63a7be0..7c5b4616ac 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -19,6 +19,8 @@ jobs: - uses: actions/labeler@v5 # Run this after labeler applied labels check-labels: + needs: + - labeler permissions: pull-requests: read name: Check labels From df977ab334f5c7703d3ae28707c24ca6fcc3d5b9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 14 Aug 2024 03:58:27 +0000 Subject: [PATCH 619/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 34c343ce77..5b60ab3928 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ ### Internal +* 👷 Update GitHub Action labeler dependencies. PR [#1302](https://github.com/fastapi/full-stack-fastapi-template/pull/1302) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action labeler permissions. PR [#1300](https://github.com/fastapi/full-stack-fastapi-template/pull/1300) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action label-checker. PR [#1299](https://github.com/fastapi/full-stack-fastapi-template/pull/1299) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action labeler. PR [#1298](https://github.com/fastapi/full-stack-fastapi-template/pull/1298) by [@tiangolo](https://github.com/tiangolo). From b17291fbf4b934be2be799359b6a2e14736cd4a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:56:53 -0500 Subject: [PATCH 620/771] :arrow_up: Bump axios from 1.6.2 to 1.7.4 in /frontend (#1301) Bumps [axios](https://github.com/axios/axios) from 1.6.2 to 1.7.4. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.6.2...v1.7.4) --- updated-dependencies: - dependency-name: axios dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com> --- frontend/package-lock.json | 18 +++++++++--------- frontend/package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1a13684c74..9560ff9f69 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,7 +15,7 @@ "@tanstack/react-query": "^5.28.14", "@tanstack/react-query-devtools": "^5.28.14", "@tanstack/react-router": "1.19.1", - "axios": "1.6.2", + "axios": "1.7.4", "form-data": "4.0.0", "framer-motion": "10.16.16", "react": "^18.2.0", @@ -2781,11 +2781,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -5737,11 +5737,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "requires": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } diff --git a/frontend/package.json b/frontend/package.json index 8533517148..b9b2e3b51a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,7 @@ "@tanstack/react-query": "^5.28.14", "@tanstack/react-query-devtools": "^5.28.14", "@tanstack/react-router": "1.19.1", - "axios": "1.6.2", + "axios": "1.7.4", "form-data": "4.0.0", "framer-motion": "10.16.16", "react": "^18.2.0", From 1605f496e94872c83fd562631c3f8be61b2e7bb4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 14 Aug 2024 14:57:14 +0000 Subject: [PATCH 621/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 5b60ab3928..cf08fce9af 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ ### Internal +* ⬆️ Bump axios from 1.6.2 to 1.7.4 in /frontend. PR [#1301](https://github.com/fastapi/full-stack-fastapi-template/pull/1301) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update GitHub Action labeler dependencies. PR [#1302](https://github.com/fastapi/full-stack-fastapi-template/pull/1302) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action labeler permissions. PR [#1300](https://github.com/fastapi/full-stack-fastapi-template/pull/1300) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action label-checker. PR [#1299](https://github.com/fastapi/full-stack-fastapi-template/pull/1299) by [@tiangolo](https://github.com/tiangolo). From a9437fb2b45b9f00610940ba067f84a901d49d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 14 Aug 2024 14:12:29 -0500 Subject: [PATCH 622/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20GitHub=20Action?= =?UTF-8?q?=20labeler=20to=20add=20only=20one=20label=20(#1304)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 24 +++++++++++++++--------- .github/workflows/labeler.yml | 5 +++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index e19f33b788..c9b267e9d0 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,12 +1,18 @@ docs: - - changed-files: - - any-glob-to-any-file: - - '**/*.md' + - all: + - changed-files: + - all-globs-to-all-files: + - '**/*.md' internal: - - changed-files: - - any-glob-to-any-file: - - .github/**/* - - scripts/**/* - - .gitignore - - .pre-commit-config.yaml + - all: + - changed-files: + - any-glob-to-any-file: + - .github/** + - scripts/** + - .gitignore + - .pre-commit-config.yaml + - all-globs-to-all-files: + - '**/*.md' + - 'frontend/**' + - 'backend/**' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 7c5b4616ac..d62f1668d5 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,4 +1,4 @@ -name: Pull Request Labeler and Checker +name: Labels on: pull_request_target: types: @@ -17,13 +17,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 + with: + sync-labels: true # Run this after labeler applied labels check-labels: needs: - labeler permissions: pull-requests: read - name: Check labels runs-on: ubuntu-latest steps: - uses: docker://agilepathway/pull-request-label-checker:latest From 60b20fa211ea8216639404aef641100889c2f43a Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 14 Aug 2024 19:12:46 +0000 Subject: [PATCH 623/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index cf08fce9af..b02848bad4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ ### Internal +* 👷 Update GitHub Action labeler to add only one label. PR [#1304](https://github.com/fastapi/full-stack-fastapi-template/pull/1304) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Bump axios from 1.6.2 to 1.7.4 in /frontend. PR [#1301](https://github.com/fastapi/full-stack-fastapi-template/pull/1301) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update GitHub Action labeler dependencies. PR [#1302](https://github.com/fastapi/full-stack-fastapi-template/pull/1302) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action labeler permissions. PR [#1300](https://github.com/fastapi/full-stack-fastapi-template/pull/1300) by [@tiangolo](https://github.com/tiangolo). From e5077bd9f424b05d32ed12e04a10161b9a815cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 15 Aug 2024 13:48:43 -0500 Subject: [PATCH 624/771] =?UTF-8?q?=F0=9F=91=B7=20Do=20not=20sync=20labels?= =?UTF-8?q?=20as=20it=20overrides=20manually=20added=20labels=20(#1307)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/labeler.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index d62f1668d5..c3bb83f9a5 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -17,8 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 - with: - sync-labels: true # Run this after labeler applied labels check-labels: needs: From 5a730c59a98f12d09e157f92afd6916a7d0ee488 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 15 Aug 2024 18:49:23 +0000 Subject: [PATCH 625/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release-notes.md b/release-notes.md index b02848bad4..b65d09c77f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,8 @@ ## Latest Changes +* 👷 Do not sync labels as it overrides manually added labels. PR [#1307](https://github.com/fastapi/full-stack-fastapi-template/pull/1307) by [@tiangolo](https://github.com/tiangolo). + ### Refactors * ♻️ Remove duplicate information in the ItemCreate model. PR [#1287](https://github.com/fastapi/full-stack-fastapi-template/pull/1287) by [@jjaakko](https://github.com/jjaakko). From 978dbdd919604ace09dfffa29f8e577f41fa28db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 15 Aug 2024 15:42:54 -0500 Subject: [PATCH 626/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20configs=20for?= =?UTF-8?q?=20labeler=20(#1308)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index c9b267e9d0..a0a1c1aab0 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,7 @@ docs: - all: - changed-files: - - all-globs-to-all-files: + - any-glob-to-any-file: - '**/*.md' internal: @@ -13,6 +13,6 @@ internal: - .gitignore - .pre-commit-config.yaml - all-globs-to-all-files: - - '**/*.md' - - 'frontend/**' - - 'backend/**' + - '!./**/*.md' + - '!frontend/**' + - '!backend/**' From 6ec1bedc5bb7085463c48533b6c05554c1bc3a8a Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 15 Aug 2024 20:43:14 +0000 Subject: [PATCH 627/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index b65d09c77f..5a867a286c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Internal +* 👷 Update configs for labeler. PR [#1308](https://github.com/fastapi/full-stack-fastapi-template/pull/1308) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action labeler to add only one label. PR [#1304](https://github.com/fastapi/full-stack-fastapi-template/pull/1304) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Bump axios from 1.6.2 to 1.7.4 in /frontend. PR [#1301](https://github.com/fastapi/full-stack-fastapi-template/pull/1301) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update GitHub Action labeler dependencies. PR [#1302](https://github.com/fastapi/full-stack-fastapi-template/pull/1302) by [@tiangolo](https://github.com/tiangolo). From 41d659c13195b27950d779233b5ef4f06c99e205 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:59:22 -0500 Subject: [PATCH 628/771] =?UTF-8?q?=F0=9F=93=9D=20Add=20Email=20Templates?= =?UTF-8?q?=20to=20`backend/README.md`=20(#1311)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/README.md b/backend/README.md index e6400be194..7e7829677f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -196,3 +196,11 @@ $ alembic upgrade head ``` If you don't want to start with the default models and want to remove them / modify them, from the beginning, without having any previous revision, you can remove the revision files (`.py` Python files) under `./backend/app/alembic/versions/`. And then create a first migration as described above. + +## Email Templates + +The email templates are in `./backend/app/email-templates/`. Here, there are two directories: `build` and `src`. The `src` directory contains the source files that are used to build the final email templates. The `build` directory contains the final email templates that are used by the application. + +Before continuing, ensure you have the [MJML extension](https://marketplace.visualstudio.com/items?itemName=attilabuti.vscode-mjml) installed in your VS Code. + +Once you have the MJML extension installed, you can create a new email template in the `src` directory. After creating the new email template and with the `.mjml` file open in your editor, open the command palette with `Ctrl+Shift+P` and search for `MJML: Export to HTML`. This will convert the `.mjml` file to a `.html` file and now you can save it in the build directory. From 25fd6ae3b99041e50b72acebf324193eb295a6d8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 17 Aug 2024 18:59:41 +0000 Subject: [PATCH 629/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 5a867a286c..4164484959 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,10 @@ * ♻️ Remove duplicate information in the ItemCreate model. PR [#1287](https://github.com/fastapi/full-stack-fastapi-template/pull/1287) by [@jjaakko](https://github.com/jjaakko). +### Docs + +* 📝 Add Email Templates to `backend/README.md`. PR [#1311](https://github.com/fastapi/full-stack-fastapi-template/pull/1311) by [@alejsdev](https://github.com/alejsdev). + ### Internal * 👷 Update configs for labeler. PR [#1308](https://github.com/fastapi/full-stack-fastapi-template/pull/1308) by [@tiangolo](https://github.com/tiangolo). From 6666071f0d0bf3c48ad4eb4b6d757e0feaa6794b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Aug 2024 21:17:32 -0500 Subject: [PATCH 630/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20`latest-changes?= =?UTF-8?q?`=20GitHub=20Action=20(#1315)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/latest-changes.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index 4bda7c0232..b41f961e41 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -30,8 +30,7 @@ jobs: with: # To allow latest-changes to commit to the main branch token: ${{ secrets.LATEST_CHANGES }} - - uses: docker://tiangolo/latest-changes:0.3.0 - # - uses: tiangolo/latest-changes@main + - uses: tiangolo/latest-changes@0.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: ./release-notes.md From b5a8a472c85dc0a873649a396278800e5face675 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 26 Aug 2024 02:18:04 +0000 Subject: [PATCH 631/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 4164484959..b3d111b139 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ ### Internal +* 👷 Update `latest-changes` GitHub Action. PR [#1315](https://github.com/fastapi/full-stack-fastapi-template/pull/1315) by [@tiangolo](https://github.com/tiangolo). * 👷 Update configs for labeler. PR [#1308](https://github.com/fastapi/full-stack-fastapi-template/pull/1308) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action labeler to add only one label. PR [#1304](https://github.com/fastapi/full-stack-fastapi-template/pull/1304) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Bump axios from 1.6.2 to 1.7.4 in /frontend. PR [#1301](https://github.com/fastapi/full-stack-fastapi-template/pull/1301) by [@dependabot[bot]](https://github.com/apps/dependabot). From 392719f08a7c538cf0faef81c9f5c92635e75c0e Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:38:22 +0200 Subject: [PATCH 632/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20`.github/labele?= =?UTF-8?q?r.yml`=20(#1321)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index a0a1c1aab0..4d6ca67063 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -3,6 +3,13 @@ docs: - changed-files: - any-glob-to-any-file: - '**/*.md' + - all-globs-to-all-files: + - '!frontend/**' + - '!backend/**' + - '!'.github/**' + - '!scripts/**' + - '!.gitignore' + - '!.pre-commit-config.yaml' internal: - all: From a75c5dec6ad1e20de1be8d4a8f8f54c2e4186489 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Aug 2024 15:38:45 +0000 Subject: [PATCH 633/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index b3d111b139..5a9f3ca30e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ ### Internal +* 👷 Update `.github/labeler.yml`. PR [#1321](https://github.com/fastapi/full-stack-fastapi-template/pull/1321) by [@alejsdev](https://github.com/alejsdev). * 👷 Update `latest-changes` GitHub Action. PR [#1315](https://github.com/fastapi/full-stack-fastapi-template/pull/1315) by [@tiangolo](https://github.com/tiangolo). * 👷 Update configs for labeler. PR [#1308](https://github.com/fastapi/full-stack-fastapi-template/pull/1308) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action labeler to add only one label. PR [#1304](https://github.com/fastapi/full-stack-fastapi-template/pull/1304) by [@tiangolo](https://github.com/tiangolo). From 0fd2777bed792876203b1d1af704f2c354b7716f Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:46:02 +0200 Subject: [PATCH 634/771] =?UTF-8?q?=F0=9F=90=9B=20Fix=20in=20`.github/labe?= =?UTF-8?q?ler.yml`=20(#1322)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 4d6ca67063..ed657c23d7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -6,7 +6,7 @@ docs: - all-globs-to-all-files: - '!frontend/**' - '!backend/**' - - '!'.github/**' + - '!.github/**' - '!scripts/**' - '!.gitignore' - '!.pre-commit-config.yaml' From 0090e8623cf052ef07023c7d7968bf14606b4a3e Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Aug 2024 15:46:18 +0000 Subject: [PATCH 635/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 5a9f3ca30e..8ac8ab56e4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ ### Internal +* 🐛 Fix in `.github/labeler.yml`. PR [#1322](https://github.com/fastapi/full-stack-fastapi-template/pull/1322) by [@alejsdev](https://github.com/alejsdev). * 👷 Update `.github/labeler.yml`. PR [#1321](https://github.com/fastapi/full-stack-fastapi-template/pull/1321) by [@alejsdev](https://github.com/alejsdev). * 👷 Update `latest-changes` GitHub Action. PR [#1315](https://github.com/fastapi/full-stack-fastapi-template/pull/1315) by [@tiangolo](https://github.com/tiangolo). * 👷 Update configs for labeler. PR [#1308](https://github.com/fastapi/full-stack-fastapi-template/pull/1308) by [@tiangolo](https://github.com/tiangolo). From cac962e7a131747045b93eb4451ebd8f1afec921 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:10:49 +0200 Subject: [PATCH 636/771] =?UTF-8?q?=F0=9F=91=B7=F0=9F=8F=BB=20Auto-generat?= =?UTF-8?q?e=20frontend=20client=20=20(#1320)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/generate-client.yml | 49 +++++++++++++++++++++++++++ frontend/README.md | 17 ++++++++-- frontend/biome.json | 1 - frontend/package.json | 2 +- frontend/src/client/core/request.ts | 6 ++-- frontend/src/client/services.ts | 48 +++++++++++++------------- scripts/generate-client.sh | 8 +++++ 7 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/generate-client.yml create mode 100644 scripts/generate-client.sh diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml new file mode 100644 index 0000000000..a81f78cb51 --- /dev/null +++ b/.github/workflows/generate-client.yml @@ -0,0 +1,49 @@ +name: Generate Client + +on: + pull_request: + types: + - opened + - synchronize + +jobs: + generate-client: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.FULL_STACK_FASTAPI_TEMPLATE_REPO_TOKEN }} + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install dependencies + run: npm ci + working-directory: frontend + - run: pip install ./backend + - run: bash scripts/generate-client.sh + - name: Commit changes + run: | + git config --local user.email "github-actions@github.com" + git config --local user.name "github-actions" + git add frontend/src/client + git diff --staged --quiet || git commit -m "✨ Autogenerate frontend client" + git push + + # https://github.com/marketplace/actions/alls-green#why + generate-client-alls-green: # This job does nothing and is only used for the branch protection + if: always() + needs: + - generate-client + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 13e3a8c488..9a01970fda 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -4,7 +4,7 @@ The frontend is built with [Vite](https://vitejs.dev/), [React](https://reactjs. ## Frontend development -Before you begin, ensure that you have either the Node Version Manager (nvm) or Fast Node Manager (fnm) installed on your system. +Before you begin, ensure that you have either the Node Version Manager (nvm) or Fast Node Manager (fnm) installed on your system. * To install fnm follow the [official fnm guide](https://github.com/Schniz/fnm#installation). If you prefer nvm, you can install it using the [official nvm guide](https://github.com/nvm-sh/nvm#installing-and-updating). @@ -27,7 +27,7 @@ nvm install ```bash # If using fnm -fnm use +fnm use # If using nvm nvm use @@ -74,6 +74,19 @@ But it would be only to clean them up, leaving them won't really have any effect ## Generate Client +### Automatically + +* Activate the backend virtual environment. +* From the top level project directory, run the script: + +```bash +./scripts/generate-frontend-client.sh +``` + +* Commit the changes. + +### Manually + * Start the Docker Compose stack. * Download the OpenAPI JSON file from `http://localhost/api/v1/openapi.json` and copy it to a new file `openapi.json` at the root of the `frontend` directory. diff --git a/frontend/biome.json b/frontend/biome.json index 14597ce328..a06315dc2a 100644 --- a/frontend/biome.json +++ b/frontend/biome.json @@ -6,7 +6,6 @@ "files": { "ignore": [ "node_modules", - "src/client/", "src/routeTree.gen.ts", "playwright.config.ts", "playwright-report" diff --git a/frontend/package.json b/frontend/package.json index b9b2e3b51a..1a7a547f68 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,7 @@ "build": "tsc && vite build", "lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./", "preview": "vite preview", - "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true && biome format --write ./src/client" + "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true" }, "dependencies": { "@chakra-ui/icons": "2.1.1", diff --git a/frontend/src/client/core/request.ts b/frontend/src/client/core/request.ts index 6abb0e8f41..99d38b46f1 100644 --- a/frontend/src/client/core/request.ts +++ b/frontend/src/client/core/request.ts @@ -1,9 +1,9 @@ import axios from "axios" import type { AxiosError, - AxiosInstance, AxiosRequestConfig, AxiosResponse, + AxiosInstance, } from "axios" import { ApiError } from "./ApiError" @@ -151,12 +151,12 @@ export const getHeaders = async ( ) if (isStringWithValue(token)) { - headers.Authorization = `Bearer ${token}` + headers["Authorization"] = `Bearer ${token}` } if (isStringWithValue(username) && isStringWithValue(password)) { const credentials = base64(`${username}:${password}`) - headers.Authorization = `Basic ${credentials}` + headers["Authorization"] = `Basic ${credentials}` } if (options.body !== undefined) { diff --git a/frontend/src/client/services.ts b/frontend/src/client/services.ts index be024e4d11..b99e4ac515 100644 --- a/frontend/src/client/services.ts +++ b/frontend/src/client/services.ts @@ -4,20 +4,20 @@ import { request as __request } from "./core/request" import type { Body_login_login_access_token, - ItemCreate, - ItemPublic, - ItemUpdate, - ItemsPublic, Message, NewPassword, Token, + UserPublic, UpdatePassword, UserCreate, - UserPublic, UserRegister, + UsersPublic, UserUpdate, UserUpdateMe, - UsersPublic, + ItemCreate, + ItemPublic, + ItemsPublic, + ItemUpdate, } from "./models" export type TDataLoginAccessToken = { @@ -50,7 +50,7 @@ export class LoginService { formData: formData, mediaType: "application/x-www-form-urlencoded", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -85,7 +85,7 @@ export class LoginService { email, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -106,7 +106,7 @@ export class LoginService { body: requestBody, mediaType: "application/json", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -128,7 +128,7 @@ export class LoginService { email, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -180,7 +180,7 @@ export class UsersService { limit, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -201,7 +201,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -248,7 +248,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -269,7 +269,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -290,7 +290,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -312,7 +312,7 @@ export class UsersService { user_id: userId, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -336,7 +336,7 @@ export class UsersService { body: requestBody, mediaType: "application/json", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -356,7 +356,7 @@ export class UsersService { user_id: userId, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -382,7 +382,7 @@ export class UtilsService { email_to: emailTo, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -425,7 +425,7 @@ export class ItemsService { limit, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -446,7 +446,7 @@ export class ItemsService { body: requestBody, mediaType: "application/json", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -466,7 +466,7 @@ export class ItemsService { id, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -490,7 +490,7 @@ export class ItemsService { body: requestBody, mediaType: "application/json", errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } @@ -510,7 +510,7 @@ export class ItemsService { id, }, errors: { - 422: "Validation Error", + 422: `Validation Error`, }, }) } diff --git a/scripts/generate-client.sh b/scripts/generate-client.sh new file mode 100644 index 0000000000..1327ee6fd1 --- /dev/null +++ b/scripts/generate-client.sh @@ -0,0 +1,8 @@ +#! /usr/bin/env bash + +PYTHONPATH=backend python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()))" > openapi.json +node frontend/modify-openapi-operationids.js +mv openapi.json frontend/ +cd frontend +npm run generate-client +npx biome format --write ./src/client From e506d28331254c33f704140f3905296c7496c76b Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Aug 2024 18:11:08 +0000 Subject: [PATCH 637/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 8ac8ab56e4..83154d17d9 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ ### Internal +* 👷🏻 Auto-generate frontend client . PR [#1320](https://github.com/fastapi/full-stack-fastapi-template/pull/1320) by [@alejsdev](https://github.com/alejsdev). * 🐛 Fix in `.github/labeler.yml`. PR [#1322](https://github.com/fastapi/full-stack-fastapi-template/pull/1322) by [@alejsdev](https://github.com/alejsdev). * 👷 Update `.github/labeler.yml`. PR [#1321](https://github.com/fastapi/full-stack-fastapi-template/pull/1321) by [@alejsdev](https://github.com/alejsdev). * 👷 Update `latest-changes` GitHub Action. PR [#1315](https://github.com/fastapi/full-stack-fastapi-template/pull/1315) by [@tiangolo](https://github.com/tiangolo). From 2352d3060b92702b60a744aa4a9d0a17f551d98a Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Mon, 2 Sep 2024 22:48:18 +0200 Subject: [PATCH 638/771] set include-hidden-files to true for actions/upload-artifact@v4 (#1327) --- .github/workflows/playwright.yml | 1 + .github/workflows/test.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d242af4f18..fc208993f6 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -52,6 +52,7 @@ jobs: name: playwright-report path: frontend/playwright-report/ retention-days: 30 + include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why e2e-alls-green: # This job does nothing and is only used for the branch protection diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03e4ba1ef6..5849d19749 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,7 @@ jobs: with: name: coverage-html path: backend/htmlcov + include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why alls-green: # This job does nothing and is only used for the branch protection From 07f5539b1f4aac650963b3504939365cbf9f8747 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 2 Sep 2024 20:48:36 +0000 Subject: [PATCH 639/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 83154d17d9..ac75632a44 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ ### Internal +* 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#1327](https://github.com/fastapi/full-stack-fastapi-template/pull/1327) by [@svlandeg](https://github.com/svlandeg). * 👷🏻 Auto-generate frontend client . PR [#1320](https://github.com/fastapi/full-stack-fastapi-template/pull/1320) by [@alejsdev](https://github.com/alejsdev). * 🐛 Fix in `.github/labeler.yml`. PR [#1322](https://github.com/fastapi/full-stack-fastapi-template/pull/1322) by [@alejsdev](https://github.com/alejsdev). * 👷 Update `.github/labeler.yml`. PR [#1321](https://github.com/fastapi/full-stack-fastapi-template/pull/1321) by [@alejsdev](https://github.com/alejsdev). From e6bf8c38209d7602155459429b951a390c08ad37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 7 Sep 2024 17:20:24 +0200 Subject: [PATCH 640/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20`issue-manager.?= =?UTF-8?q?yml`=20(#1329)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/issue-manager.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index f172dfb6a4..b50a77f185 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -2,7 +2,7 @@ name: Issue Manager on: schedule: - - cron: "13 4 * * *" + - cron: "21 17 * * *" issue_comment: types: - created @@ -16,6 +16,7 @@ on: permissions: issues: write + pull-requests: write jobs: issue-manager: @@ -35,8 +36,8 @@ jobs: "delay": 864000, "message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs." }, - "changes-requested": { + "waiting": { "delay": 2628000, - "message": "As this PR had requested changes to be applied but has been inactive for a while, it's now going to be closed. But if there's anyone interested, feel free to create a new PR." + "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR." } } From 6699631b8900112d444accb2c6cf6d715b66706c Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 7 Sep 2024 15:20:44 +0000 Subject: [PATCH 641/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index ac75632a44..b823906939 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ ### Internal +* 👷 Update `issue-manager.yml`. PR [#1329](https://github.com/fastapi/full-stack-fastapi-template/pull/1329) by [@tiangolo](https://github.com/tiangolo). * 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#1327](https://github.com/fastapi/full-stack-fastapi-template/pull/1327) by [@svlandeg](https://github.com/svlandeg). * 👷🏻 Auto-generate frontend client . PR [#1320](https://github.com/fastapi/full-stack-fastapi-template/pull/1320) by [@alejsdev](https://github.com/alejsdev). * 🐛 Fix in `.github/labeler.yml`. PR [#1322](https://github.com/fastapi/full-stack-fastapi-template/pull/1322) by [@alejsdev](https://github.com/alejsdev). From 6528194f38be2910be255701f9724000bb3a2c08 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Thu, 12 Sep 2024 15:21:24 +0200 Subject: [PATCH 642/771] =?UTF-8?q?=F0=9F=91=B7=20Improve=20playwright=20C?= =?UTF-8?q?I=20job=20(#1335)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/playwright.yml | 4 ++-- backend/app/api/routes/utils.py | 5 +++++ docker-compose.yml | 6 ++++++ frontend/src/client/services.ts | 12 ++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index fc208993f6..60f4ceac14 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -41,9 +41,9 @@ jobs: working-directory: frontend - run: docker compose build - run: docker compose down -v --remove-orphans - - run: docker compose up -d + - run: docker compose up -d --wait - name: Run Playwright tests - run: npx playwright test + run: npx playwright test --fail-on-flaky-tests --trace=retain-on-failure working-directory: frontend - run: docker compose down -v --remove-orphans - uses: actions/upload-artifact@v4 diff --git a/backend/app/api/routes/utils.py b/backend/app/api/routes/utils.py index 82f6d2b821..a73b80d761 100644 --- a/backend/app/api/routes/utils.py +++ b/backend/app/api/routes/utils.py @@ -24,3 +24,8 @@ def test_email(email_to: EmailStr) -> Message: html_content=email_data.html_content, ) return Message(message="Test email sent") + + +@router.get("/health-check/") +async def health_check() -> bool: + return True diff --git a/docker-compose.yml b/docker-compose.yml index d614942cbd..3c2d79cc32 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,6 +63,12 @@ services: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - SENTRY_DSN=${SENTRY_DSN} + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/api/v1/utils/health-check/"] + interval: 10s + timeout: 5s + retries: 5 + build: context: ./backend args: diff --git a/frontend/src/client/services.ts b/frontend/src/client/services.ts index b99e4ac515..a7d58e9c02 100644 --- a/frontend/src/client/services.ts +++ b/frontend/src/client/services.ts @@ -386,6 +386,18 @@ export class UtilsService { }, }) } + + /** + * Health Check + * @returns boolean Successful Response + * @throws ApiError + */ + public static healthCheck(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/utils/health-check/", + }) + } } export type TDataReadItems = { From bdb283f6ba38363c553ea84e09707294e2a04dc7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 12 Sep 2024 13:21:44 +0000 Subject: [PATCH 643/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index b823906939..c505e9a5ff 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ ### Internal +* 👷 Improve playwright CI job. PR [#1335](https://github.com/fastapi/full-stack-fastapi-template/pull/1335) by [@patrick91](https://github.com/patrick91). * 👷 Update `issue-manager.yml`. PR [#1329](https://github.com/fastapi/full-stack-fastapi-template/pull/1329) by [@tiangolo](https://github.com/tiangolo). * 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#1327](https://github.com/fastapi/full-stack-fastapi-template/pull/1327) by [@svlandeg](https://github.com/svlandeg). * 👷🏻 Auto-generate frontend client . PR [#1320](https://github.com/fastapi/full-stack-fastapi-template/pull/1320) by [@alejsdev](https://github.com/alejsdev). From a5ea4c4c547306894b88f7cfe9fe1453636e7696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 16 Sep 2024 21:42:50 +0200 Subject: [PATCH 644/771] =?UTF-8?q?=F0=9F=94=A5=20Enable=20support=20for?= =?UTF-8?q?=20Arm=20Docker=20images=20in=20Mac,=20remove=20old=20patch=20(?= =?UTF-8?q?#1341)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3c2d79cc32..678e949e92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,7 +73,6 @@ services: context: ./backend args: INSTALL_DEV: ${INSTALL_DEV-false} - platform: linux/amd64 # Patch for M1 Mac labels: - traefik.enable=true - traefik.docker.network=traefik-public From fc90d2e1f99ee2a5bf9fad044cbb4c22396dca5a Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 16 Sep 2024 19:43:09 +0000 Subject: [PATCH 645/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index c505e9a5ff..3f91a18dfe 100644 --- a/release-notes.md +++ b/release-notes.md @@ -6,6 +6,7 @@ ### Refactors +* 🔥 Enable support for Arm Docker images in Mac, remove old patch. PR [#1341](https://github.com/fastapi/full-stack-fastapi-template/pull/1341) by [@tiangolo](https://github.com/tiangolo). * ♻️ Remove duplicate information in the ItemCreate model. PR [#1287](https://github.com/fastapi/full-stack-fastapi-template/pull/1287) by [@jjaakko](https://github.com/jjaakko). ### Docs From 0373af98b1fe261b4c7b77a979e72d64d71f76c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 16 Sep 2024 21:52:33 +0200 Subject: [PATCH 646/771] =?UTF-8?q?=F0=9F=A9=BA=20Add=20DB=20healthcheck?= =?UTF-8?q?=20(#1342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 678e949e92..056b7aafcb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,12 @@ services: db: image: postgres:12 restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + retries: 5 + start_period: 30s + timeout: 10s volumes: - app-db-data:/var/lib/postgresql/data/pgdata env_file: @@ -42,7 +48,9 @@ services: - traefik-public - default depends_on: - - db + db: + condition: service_healthy + restart: true env_file: - .env environment: From 7da0d7d12c0b9ffa6826cae2525cfdcab5b6761f Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 16 Sep 2024 19:52:57 +0000 Subject: [PATCH 647/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 3f91a18dfe..6b86351061 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,10 @@ * 👷 Do not sync labels as it overrides manually added labels. PR [#1307](https://github.com/fastapi/full-stack-fastapi-template/pull/1307) by [@tiangolo](https://github.com/tiangolo). +### Features + +* 🩺 Add DB healthcheck. PR [#1342](https://github.com/fastapi/full-stack-fastapi-template/pull/1342) by [@tiangolo](https://github.com/tiangolo). + ### Refactors * 🔥 Enable support for Arm Docker images in Mac, remove old patch. PR [#1341](https://github.com/fastapi/full-stack-fastapi-template/pull/1341) by [@tiangolo](https://github.com/tiangolo). From 5a0ce0c0ee83f0be6c80e0eb77df8966c992667b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 17 Sep 2024 00:26:26 +0200 Subject: [PATCH 648/771] =?UTF-8?q?=F0=9F=94=A5=20Simplify=20Traefik,=20re?= =?UTF-8?q?move=20www-redirects=20that=20add=20complexity=20(#1343)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 056b7aafcb..93bfadbce9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -88,22 +88,16 @@ services: - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=(Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`)) && (PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`)) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`${DOMAIN?Variable not set}`) && (PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`)) - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=(Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`)) && (PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`)) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`${DOMAIN?Variable not set}`) && (PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`)) - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le - # Define Traefik Middleware to handle domain with and without "www" to redirect to only one - - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.regex=^http(s)?://www.(${DOMAIN?Variable not set})/(.*) - # Redirect a domain with www to non-www - - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.replacement=http$${1}://${DOMAIN?Variable not set}/$${3} - - # Enable www redirection for HTTP and HTTPS - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect,${STACK_NAME?Variable not set}-www-redirect - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.middlewares=${STACK_NAME?Variable not set}-www-redirect + # Enable redirection for HTTP and HTTPS + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect frontend: image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' @@ -123,17 +117,16 @@ services: - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le - # Enable www redirection for HTTP and HTTPS - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.middlewares=${STACK_NAME?Variable not set}-www-redirect - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.middlewares=https-redirect,${STACK_NAME?Variable not set}-www-redirect + # Enable redirection for HTTP and HTTPS + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.middlewares=https-redirect volumes: app-db-data: From 0c0b942460aee62ff786830a3471ea4f91d8c5b4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 16 Sep 2024 22:26:45 +0000 Subject: [PATCH 649/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 6b86351061..ec55282fc5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* 🔥 Simplify Traefik, remove www-redirects that add complexity. PR [#1343](https://github.com/fastapi/full-stack-fastapi-template/pull/1343) by [@tiangolo](https://github.com/tiangolo). * 🔥 Enable support for Arm Docker images in Mac, remove old patch. PR [#1341](https://github.com/fastapi/full-stack-fastapi-template/pull/1341) by [@tiangolo](https://github.com/tiangolo). * ♻️ Remove duplicate information in the ItemCreate model. PR [#1287](https://github.com/fastapi/full-stack-fastapi-template/pull/1287) by [@jjaakko](https://github.com/jjaakko). From 36dc92043220935cc773c4bd1f62a4d72393b698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 19 Sep 2024 20:11:33 +0200 Subject: [PATCH 650/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20domains?= =?UTF-8?q?=20with=20`api.example.com`=20for=20API=20and=20`dashboard.exam?= =?UTF-8?q?ple.com`=20for=20frontend,=20improve=20local=20development=20wi?= =?UTF-8?q?th=20`localhost`=20(#1344)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/playwright.yml | 2 +- backend/README.md | 52 +++----------- backend/app/core/config.py | 10 +-- backend/app/utils.py | 4 +- deployment.md | 14 ++-- development.md | 120 +++++++++++++++++++------------ docker-compose.override.yml | 10 ++- docker-compose.yml | 12 ++-- frontend/.env | 2 +- frontend/README.md | 6 +- 10 files changed, 114 insertions(+), 118 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 60f4ceac14..a884800227 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -41,7 +41,7 @@ jobs: working-directory: frontend - run: docker compose build - run: docker compose down -v --remove-orphans - - run: docker compose up -d --wait + - run: docker compose up -d --wait backend mailcatcher - name: Run Playwright tests run: npx playwright test --fail-on-flaky-tests --trace=retain-on-failure working-directory: frontend diff --git a/backend/README.md b/backend/README.md index 7e7829677f..e7782d43e6 100644 --- a/backend/README.md +++ b/backend/README.md @@ -5,45 +5,11 @@ * [Docker](https://www.docker.com/). * [Poetry](https://python-poetry.org/) for Python package and environment management. -## Local Development +## Docker Compose -* Start the stack with Docker Compose: +Start the local development environment with Docker Compose following the guide in [../development.md](../development.md). -```bash -docker compose up -d -``` - -* Now you can open your browser and interact with these URLs: - -Frontend, built with Docker, with routes handled based on the path: http://localhost - -Backend, JSON based web API based on OpenAPI: http://localhost/api/ - -Automatic interactive documentation with Swagger UI (from the OpenAPI backend): http://localhost/docs - -Adminer, database web administration: http://localhost:8080 - -Traefik UI, to see how the routes are being handled by the proxy: http://localhost:8090 - -**Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it. - -To check the logs, run: - -```bash -docker compose logs -``` - -To check the logs of a specific service, add the name of the service, e.g.: - -```bash -docker compose logs backend -``` - -If your Docker is not running in `localhost` (the URLs above wouldn't work) you would need to use the IP or domain where your Docker is running. - -## Backend local development, additional details - -### General workflow +## General Workflow By default, the dependencies are managed with [Poetry](https://python-poetry.org/), go there and install it. @@ -63,13 +29,13 @@ Make sure your editor is using the correct Python virtual environment. Modify or add SQLModel models for data and SQL tables in `./backend/app/models.py`, API endpoints in `./backend/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/crud.py`. -### VS Code +## VS Code There are already configurations in place to run the backend through the VS Code debugger, so that you can use breakpoints, pause and explore variables, etc. The setup is also already configured so you can run the tests through the VS Code Python tests tab. -### Docker Compose Override +## Docker Compose Override During development, you can change Docker Compose settings that will only affect the local development environment in the file `docker-compose.override.yml`. @@ -123,7 +89,7 @@ Nevertheless, if it doesn't detect a change but a syntax error, it will just sto ...this previous detail is what makes it useful to have the container alive doing nothing and then, in a Bash session, make it run the live reload server. -### Backend tests +## Backend tests To test the backend run: @@ -135,7 +101,7 @@ The tests run with Pytest, modify and add tests to `./backend/app/tests/`. If you use GitHub Actions the tests will run automatically. -#### Test running stack +### Test running stack If your stack is already up and you just want to run the tests, you can use: @@ -151,11 +117,11 @@ For example, to stop on first error: docker compose exec backend bash /app/tests-start.sh -x ``` -#### Test Coverage +### Test Coverage When the tests are run, a file `htmlcov/index.html` is generated, you can open it in your browser to see the coverage of the tests. -### Migrations +## Migrations As during local development your app directory is mounted as a volume inside the container, you can also run the migrations with `alembic` commands inside the container and the migration code will be in your app directory (instead of being only inside the container). So you can add it to your git repository. diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 1e3a440c1c..464aa9d353 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -31,17 +31,9 @@ class Settings(BaseSettings): SECRET_KEY: str = secrets.token_urlsafe(32) # 60 minutes * 24 hours * 8 days = 8 days ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 - DOMAIN: str = "localhost" + FRONTEND_HOST: str = "http://localhost:5173" ENVIRONMENT: Literal["local", "staging", "production"] = "local" - @computed_field # type: ignore[prop-decorator] - @property - def server_host(self) -> str: - # Use HTTPS for anything other than local development - if self.ENVIRONMENT == "local": - return f"http://{self.DOMAIN}" - return f"https://{self.DOMAIN}" - BACKEND_CORS_ORIGINS: Annotated[ list[AnyUrl] | str, BeforeValidator(parse_cors) ] = [] diff --git a/backend/app/utils.py b/backend/app/utils.py index d5ccf3153f..267993745e 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -64,7 +64,7 @@ def generate_test_email(email_to: str) -> EmailData: def generate_reset_password_email(email_to: str, email: str, token: str) -> EmailData: project_name = settings.PROJECT_NAME subject = f"{project_name} - Password recovery for user {email}" - link = f"{settings.server_host}/reset-password?token={token}" + link = f"{settings.FRONTEND_HOST}/reset-password?token={token}" html_content = render_email_template( template_name="reset_password.html", context={ @@ -90,7 +90,7 @@ def generate_new_account_email( "username": username, "password": password, "email": email_to, - "link": settings.server_host, + "link": settings.FRONTEND_HOST, }, ) return EmailData(html_content=html_content, subject=subject) diff --git a/deployment.md b/deployment.md index 6bcbe40259..9301f8091b 100644 --- a/deployment.md +++ b/deployment.md @@ -12,7 +12,7 @@ But you have to configure a couple things first. 🤓 * Have a remote server ready and available. * Configure the DNS records of your domain to point to the IP of the server you just created. -* Configure a wildcard subdomain for your domain, so that you can have multiple subdomains for different services, e.g. `*.fastapi-project.example.com`. This will be useful for accessing different components, like `traefik.fastapi-project.example.com`, `adminer.fastapi-project.example.com`, etc. And also for `staging`, like `staging.fastapi-project.example.com`, `staging.adminer.fastapi-project.example.com`, etc. +* Configure a wildcard subdomain for your domain, so that you can have multiple subdomains for different services, e.g. `*.fastapi-project.example.com`. This will be useful for accessing different components, like `dashboard.fastapi-project.example.com`, `api.fastapi-project.example.com`, `traefik.fastapi-project.example.com`, `adminer.fastapi-project.example.com`, etc. And also for `staging`, like `dashboard.staging.fastapi-project.example.com`, `adminer.staging..fastapi-project.example.com`, etc. * Install and configure [Docker](https://docs.docker.com/engine/install/) on the remote server (Docker Engine, not Docker Desktop). ## Public Traefik @@ -284,20 +284,20 @@ Traefik UI: `https://traefik.fastapi-project.example.com` ### Production -Frontend: `https://fastapi-project.example.com` +Frontend: `https://dashboard.fastapi-project.example.com` -Backend API docs: `https://fastapi-project.example.com/docs` +Backend API docs: `https://api.fastapi-project.example.com/docs` -Backend API base URL: `https://fastapi-project.example.com/api/` +Backend API base URL: `https://api.fastapi-project.example.com` Adminer: `https://adminer.fastapi-project.example.com` ### Staging -Frontend: `https://staging.fastapi-project.example.com` +Frontend: `https://dashboard.staging.fastapi-project.example.com` -Backend API docs: `https://staging.fastapi-project.example.com/docs` +Backend API docs: `https://api.staging.fastapi-project.example.com/docs` -Backend API base URL: `https://staging.fastapi-project.example.com/api/` +Backend API base URL: `https://api.staging.fastapi-project.example.com` Adminer: `https://adminer.staging.fastapi-project.example.com` diff --git a/development.md b/development.md index 857a4e0a38..38d17eb2cc 100644 --- a/development.md +++ b/development.md @@ -1,78 +1,100 @@ # FastAPI Project - Development -## Development in `localhost` with a custom domain +## Docker Compose -You might want to use something different than `localhost` as the domain. For example, if you are having problems with cookies that need a subdomain, and Chrome is not allowing you to use `localhost`. +* Start the local stack with Docker Compose: -In that case, you have two options: you could use the instructions to modify your system `hosts` file with the instructions below in **Development with a custom IP** or you can just use `localhost.tiangolo.com`, it is set up to point to `localhost` (to the IP `127.0.0.1`) and all its subdomains too. And as it is an actual domain, the browsers will store the cookies you set during development, etc. - -If you used the default CORS enabled domains while generating the project, `localhost.tiangolo.com` was configured to be allowed. If you didn't, you will need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. - -To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `localhost.tiangolo.com`. +```bash +docker compose up -d +``` -After performing those steps you should be able to open: http://localhost.tiangolo.com and it will be served by your stack in `localhost`. +* Now you can open your browser and interact with these URLs: -Check all the corresponding available URLs in the section at the end. +Frontend, built with Docker, with routes handled based on the path: http://localhost:5173 -## Development with a custom IP +Backend, JSON based web API based on OpenAPI: http://localhost:8000 -If you are running Docker in an IP address different than `127.0.0.1` (`localhost`), you will need to perform some additional steps. That will be the case if you are running a custom Virtual Machine or your Docker is located in a different machine in your network. +Automatic interactive documentation with Swagger UI (from the OpenAPI backend): http://localhost:8000/docs -In that case, you will need to use a fake local domain (`dev.example.com`) and make your computer think that the domain is served by the custom IP (e.g. `192.168.99.150`). +Adminer, database web administration: http://localhost:8080 -If you have a custom domain like that, you need to add it to the list in the variable `BACKEND_CORS_ORIGINS` in the `.env` file. +Traefik UI, to see how the routes are being handled by the proxy: http://localhost:8090 -* Open your `hosts` file with administrative privileges using a text editor: +**Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it. - * **Note for Windows**: If you are in Windows, open the main Windows menu, search for "notepad", right click on it, and select the option "open as Administrator" or similar. Then click the "File" menu, "Open file", go to the directory `c:\Windows\System32\Drivers\etc\`, select the option to show "All files" instead of only "Text (.txt) files", and open the `hosts` file. - * **Note for Mac and Linux**: Your `hosts` file is probably located at `/etc/hosts`, you can edit it in a terminal running `sudo nano /etc/hosts`. +To check the logs, run: -* Additional to the contents it might have, add a new line with the custom IP (e.g. `192.168.99.150`) a space character, and your fake local domain: `dev.example.com`. +```bash +docker compose logs +``` -The new line might look like: +To check the logs of a specific service, add the name of the service, e.g.: -``` -192.168.99.150 dev.example.com +```bash +docker compose logs backend ``` -* Save the file. - * **Note for Windows**: Make sure you save the file as "All files", without an extension of `.txt`. By default, Windows tries to add the extension. Make sure the file is saved as is, without extension. +## Local Development -...that will make your computer think that the fake local domain is served by that custom IP, and when you open that URL in your browser, it will talk directly to your locally running server when it is asked to go to `dev.example.com` and think that it is a remote server while it is actually running in your computer. +The Docker Compose files are configured so that each of the services is available in a different port in `localhost`. -To configure it in your stack, follow the section **Change the development "domain"** below, using the domain `dev.example.com`. +For the backend and frontend, they use the same port that would be used by their local development server, so, the backend is at `http://localhost:8000` and the frontend at `http://localhost:5173`. -After performing those steps you should be able to open: http://dev.example.com and it will be server by your stack in `192.168.99.150`. +This way, you could turn off a Docker Compose service and start its local development service, and everything would keep working, because it all uses the same ports. -Check all the corresponding available URLs in the section at the end. +For example, you can stop that `frontend` service in the Docker Compose: -## Change the development "domain" +```bash +docker compose stop frontend +``` -If you need to use your local stack with a different domain than `localhost`, you need to make sure the domain you use points to the IP where your stack is set up. +And then start the local frontend development server: -To simplify your Docker Compose setup, for example, so that the API docs (Swagger UI) knows where is your API, you should let it know you are using that domain for development. +```bash +cd frontend +npm run dev +``` -* Open the file located at `./.env`. It would have a line like: +Or you could stop the `backend` Docker Compose service: -``` -DOMAIN=localhost +```bash +docker compose stop backend ``` -* Change it to the domain you are going to use, e.g.: +And then you can run the local development server for the backend: +```bash +cd backend +fastapi dev app/main.py ``` + +## Docker Compose in `localhost.tiangolo.com` + +When you start the Docker Compose stack, it uses `localhost` by default, with different ports for each service (backend, frontend, adminer, etc). + +When you deploy it to production (or staging), it will deploy each service in a different subdomain, like `api.example.com` for the backend and `dashboard.example.com` for the frontend. + +In the guide about [deployment](deployment.md) you can read about Traefik, the configured proxy. That's the component in charge of transmitting traffic to each service based on the subdomain. + +If you want to test that it's all working locally, you can edit the local `.env` file, and change: + +```dotenv DOMAIN=localhost.tiangolo.com ``` -That variable will be used by the Docker Compose files. +That will be used by the Docker Compose files to configure the base domain for the services. -After that, you can restart your stack with: +Traefik will use this to transmit traffic at `api.localhost.tiangolo.com` to the backend, and traffic at `dashboard.localhost.tiangolo.com` to the frontend. + +The domain `localhost.tiangolo.com` is a special domain that is configured (with all its subdomains) to point to `127.0.0.1`. This way you can use that for your local development. + +After you update it, run again: ```bash docker compose up -d ``` -and check all the corresponding available URLs in the section at the end. +When deploying, for example in production, the main Traefik is configured outside of the Docker Compose files. For local development, there's an included Traefik in `docker-compose.override.yml`, just to let you test that the domains work as expected, for example with `api.localhost.tiangolo.com` and `dashboard.localhost.tiangolo.com`. ## Docker Compose files and env vars @@ -84,6 +106,12 @@ These Docker Compose files use the `.env` file containing configurations to be i They also use some additional configurations taken from environment variables set in the scripts before calling the `docker compose` command. +After changing variables, make sure you restart the stack: + +```bash +docker compose up -d +``` + ## The .env file The `.env` file is the one that contains all your configurations, generated keys and passwords, etc. @@ -92,7 +120,7 @@ Depending on your workflow, you could want to exclude it from Git, for example i One way to do it could be to add each environment variable to your CI/CD system, and updating the `docker-compose.yml` file to read that specific env var instead of reading the `.env` file. -### Pre-commits and code linting +## Pre-commits and code linting we are using a tool called [pre-commit](https://pre-commit.com/) for code linting and formatting. @@ -146,29 +174,29 @@ The production or staging URLs would use these same paths, but with your own dom Development URLs, for local development. -Frontend: http://localhost +Frontend: http://localhost:5173 -Backend: http://localhost/api/ +Backend: http://localhost:8000 -Automatic Interactive Docs (Swagger UI): http://localhost/docs +Automatic Interactive Docs (Swagger UI): http://localhost:8000/docs -Automatic Alternative Docs (ReDoc): http://localhost/redoc +Automatic Alternative Docs (ReDoc): http://localhost:8000/redoc Adminer: http://localhost:8080 Traefik UI: http://localhost:8090 -### Development in localhost with a custom domain URLs +### Development URLs with `localhost.tiangolo.com` Configured Development URLs, for local development. -Frontend: http://localhost.tiangolo.com +Frontend: http://dashboard.localhost.tiangolo.com -Backend: http://localhost.tiangolo.com/api/ +Backend: http://api.localhost.tiangolo.com -Automatic Interactive Docs (Swagger UI): http://localhost.tiangolo.com/docs +Automatic Interactive Docs (Swagger UI): http://api.localhost.tiangolo.comdocs -Automatic Alternative Docs (ReDoc): http://localhost.tiangolo.com/redoc +Automatic Alternative Docs (ReDoc): http://api.localhost.tiangolo.comredoc Adminer: http://localhost.tiangolo.com:8080 diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 418b535ab6..55965e4cf1 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,5 +1,10 @@ services: + # Local services are available on their ports, but also available on: + # http://api.localhost.tiangolo.com: backend + # http://dashboard.localhost.tiangolo.com: frontend + # etc. To enable it, update .env, set: + # DOMAIN=localhost.tiangolo.com proxy: image: traefik:3.0 volumes: @@ -54,6 +59,7 @@ services: restart: "no" ports: - "8888:8888" + - "8000:80" volumes: - ./backend/:/app build: @@ -76,10 +82,12 @@ services: frontend: restart: "no" + ports: + - "5173:80" build: context: ./frontend args: - - VITE_API_URL=http://${DOMAIN?Variable not set} + - VITE_API_URL=http://localhost:8000 - NODE_ENV=development networks: diff --git a/docker-compose.yml b/docker-compose.yml index 93bfadbce9..d65519135d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,5 @@ services: + db: image: postgres:12 restart: always @@ -55,6 +56,7 @@ services: - .env environment: - DOMAIN=${DOMAIN} + - FRONTEND_HOST=${FRONTEND_HOST?Variable not set} - ENVIRONMENT=${ENVIRONMENT} - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS} - SECRET_KEY=${SECRET_KEY?Variable not set} @@ -88,10 +90,10 @@ services: - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`${DOMAIN?Variable not set}`) && (PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`)) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`api.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http - - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`${DOMAIN?Variable not set}`) && (PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`)) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`api.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le @@ -108,7 +110,7 @@ services: build: context: ./frontend args: - - VITE_API_URL=https://${DOMAIN?Variable not set} + - VITE_API_URL=https://api.${DOMAIN?Variable not set} - NODE_ENV=production labels: - traefik.enable=true @@ -117,10 +119,10 @@ services: - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`dashboard.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`dashboard.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le diff --git a/frontend/.env b/frontend/.env index f829bd1979..5934e2e7d2 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1 @@ -VITE_API_URL=http://localhost +VITE_API_URL=http://localhost:8000 diff --git a/frontend/README.md b/frontend/README.md index 9a01970fda..fde8267842 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -112,7 +112,7 @@ Notice that everytime the backend changes (changing the OpenAPI schema), you sho If you want to use a remote API, you can set the environment variable `VITE_API_URL` to the URL of the remote API. For example, you can set it in the `frontend/.env` file: ```env -VITE_API_URL=https://my-remote-api.example.com +VITE_API_URL=https://api.my-domain.example.com ``` Then, when you run the frontend, it will use that URL as the base URL for the API. @@ -134,7 +134,7 @@ The frontend code is structured as follows: The frontend includes initial end-to-end tests using Playwright. To run the tests, you need to have the Docker Compose stack running. Start the stack with the following command: ```bash -docker compose up -d +docker compose up -d --wait backend ``` Then, you can run the tests with the following command: @@ -157,4 +157,4 @@ docker compose down -v To update the tests, navigate to the tests directory and modify the existing test files or add new ones as needed. -For more information on writing and running Playwright tests, refer to the official [Playwright documentation](https://playwright.dev/docs/intro). \ No newline at end of file +For more information on writing and running Playwright tests, refer to the official [Playwright documentation](https://playwright.dev/docs/intro). From 0898ffd810999d344a94decf3680b6c6084d962a Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 19 Sep 2024 18:11:52 +0000 Subject: [PATCH 651/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index ec55282fc5..0f2632253f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* ♻️ Simplify domains with `api.example.com` for API and `dashboard.example.com` for frontend, improve local development with `localhost`. PR [#1344](https://github.com/fastapi/full-stack-fastapi-template/pull/1344) by [@tiangolo](https://github.com/tiangolo). * 🔥 Simplify Traefik, remove www-redirects that add complexity. PR [#1343](https://github.com/fastapi/full-stack-fastapi-template/pull/1343) by [@tiangolo](https://github.com/tiangolo). * 🔥 Enable support for Arm Docker images in Mac, remove old patch. PR [#1341](https://github.com/fastapi/full-stack-fastapi-template/pull/1341) by [@tiangolo](https://github.com/tiangolo). * ♻️ Remove duplicate information in the ItemCreate model. PR [#1287](https://github.com/fastapi/full-stack-fastapi-template/pull/1287) by [@jjaakko](https://github.com/jjaakko). From 8922f94676b88e17b8a355e8f76bf02473730662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 20 Sep 2024 14:43:32 +0200 Subject: [PATCH 652/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Include=20`FRONTEN?= =?UTF-8?q?D=5FHOST`=20in=20CORS=20origins=20by=20default=20(#1348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/core/config.py | 7 +++++++ backend/app/main.py | 6 ++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 464aa9d353..2a4e9fd0ba 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -38,6 +38,13 @@ class Settings(BaseSettings): list[AnyUrl] | str, BeforeValidator(parse_cors) ] = [] + @computed_field # type: ignore[prop-decorator] + @property + def all_cors_origins(self) -> list[str]: + return [str(origin).rstrip("/") for origin in self.BACKEND_CORS_ORIGINS] + [ + self.FRONTEND_HOST + ] + PROJECT_NAME: str SENTRY_DSN: HttpUrl | None = None POSTGRES_SERVER: str diff --git a/backend/app/main.py b/backend/app/main.py index 4c252a1722..9a95801e74 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -21,12 +21,10 @@ def custom_generate_unique_id(route: APIRoute) -> str: ) # Set all CORS enabled origins -if settings.BACKEND_CORS_ORIGINS: +if settings.all_cors_origins: app.add_middleware( CORSMiddleware, - allow_origins=[ - str(origin).strip("/") for origin in settings.BACKEND_CORS_ORIGINS - ], + allow_origins=settings.all_cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], From 3a5d15b3938afd3ad512d3aa587c470707d131e0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 20 Sep 2024 12:43:51 +0000 Subject: [PATCH 653/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 0f2632253f..7f3e8e8ed3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* ♻️ Include `FRONTEND_HOST` in CORS origins by default. PR [#1348](https://github.com/fastapi/full-stack-fastapi-template/pull/1348) by [@tiangolo](https://github.com/tiangolo). * ♻️ Simplify domains with `api.example.com` for API and `dashboard.example.com` for frontend, improve local development with `localhost`. PR [#1344](https://github.com/fastapi/full-stack-fastapi-template/pull/1344) by [@tiangolo](https://github.com/tiangolo). * 🔥 Simplify Traefik, remove www-redirects that add complexity. PR [#1343](https://github.com/fastapi/full-stack-fastapi-template/pull/1343) by [@tiangolo](https://github.com/tiangolo). * 🔥 Enable support for Arm Docker images in Mac, remove old patch. PR [#1341](https://github.com/fastapi/full-stack-fastapi-template/pull/1341) by [@tiangolo](https://github.com/tiangolo). From f1055cc29197c11f8da0e54c0803c84c3986345b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 20 Sep 2024 14:52:45 +0200 Subject: [PATCH 654/771] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20FastAPI?= =?UTF-8?q?=20(#1349)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/poetry.lock | 138 ++++++++++++++++++++++++++++++++++++++--- backend/pyproject.toml | 3 +- 2 files changed, 130 insertions(+), 11 deletions(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index f56ce480f0..4ba8fba010 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -428,22 +428,47 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.109.2" +version = "0.114.2" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"}, - {file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"}, + {file = "fastapi-0.114.2-py3-none-any.whl", hash = "sha256:44474a22913057b1acb973ab90f4b671ba5200482e7622816d79105dcece1ac5"}, + {file = "fastapi-0.114.2.tar.gz", hash = "sha256:0adb148b62edb09e8c6eeefa3ea934e8f276dabc038c5a82989ea6346050c3da"}, ] [package.dependencies] +email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"standard\""} +fastapi-cli = {version = ">=0.0.5", extras = ["standard"], optional = true, markers = "extra == \"standard\""} +httpx = {version = ">=0.23.0", optional = true, markers = "extra == \"standard\""} +jinja2 = {version = ">=2.11.2", optional = true, markers = "extra == \"standard\""} pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.36.3,<0.37.0" +python-multipart = {version = ">=0.0.7", optional = true, markers = "extra == \"standard\""} +starlette = ">=0.37.2,<0.39.0" typing-extensions = ">=4.8.0" +uvicorn = {version = ">=0.12.0", extras = ["standard"], optional = true, markers = "extra == \"standard\""} [package.extras] -all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fastapi-cli" +version = "0.0.5" +description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi_cli-0.0.5-py3-none-any.whl", hash = "sha256:e94d847524648c748a5350673546bbf9bcaeb086b33c24f2e82e021436866a46"}, + {file = "fastapi_cli-0.0.5.tar.gz", hash = "sha256:d30e1239c6f46fcb95e606f02cdda59a1e2fa778a54b64686b3ff27f6211ff9f"}, +] + +[package.dependencies] +typer = ">=0.12.3" +uvicorn = {version = ">=0.15.0", extras = ["standard"]} + +[package.extras] +standard = ["uvicorn[standard] (>=0.15.0)"] [[package]] name = "filelock" @@ -887,6 +912,30 @@ babel = ["Babel"] lingua = ["lingua"] testing = ["pytest"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "2.1.5" @@ -956,6 +1005,17 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "more-itertools" version = "10.3.0" @@ -1367,6 +1427,20 @@ azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0 toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pyjwt" version = "2.8.0" @@ -1529,6 +1603,24 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rich" +version = "13.8.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, + {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "ruff" version = "0.2.2" @@ -1603,6 +1695,17 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + [[package]] name = "six" version = "1.16.0" @@ -1729,13 +1832,13 @@ SQLAlchemy = ">=2.0.14,<2.1.0" [[package]] name = "starlette" -version = "0.36.3" +version = "0.38.5" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"}, - {file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"}, + {file = "starlette-0.38.5-py3-none-any.whl", hash = "sha256:632f420a9d13e3ee2a6f18f437b0a9f1faecb0bc42e1942aa2ea0e379a4c4206"}, + {file = "starlette-0.38.5.tar.gz", hash = "sha256:04a92830a9b6eb1442c766199d62260c3d4dc9c4f9188360626b1e0273cb7077"}, ] [package.dependencies] @@ -1770,6 +1873,23 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "typer" +version = "0.12.5" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +files = [ + {file = "typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b"}, + {file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + [[package]] name = "types-passlib" version = "1.7.7.20240327" @@ -2081,4 +2201,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "7ec220bee66b5bc207f9a8b2f4ca9100da0213bb9d0a407b51cac3dc8201e97c" +content-hash = "62870188540a73228ca7eac167e3ed03673048fff89103fad52998d5023cdd0e" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 671a864645..069dc791cc 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -6,8 +6,7 @@ authors = ["Admin "] [tool.poetry.dependencies] python = "^3.10" -uvicorn = {extras = ["standard"], version = "^0.24.0.post1"} -fastapi = "^0.109.1" +fastapi = {extras = ["standard"], version = "^0.114.2"} python-multipart = "^0.0.7" email-validator = "^2.1.0.post1" passlib = {extras = ["bcrypt"], version = "^1.7.4"} From 14c83b5a69e9dca3a1e5d6e8e40532562a539b50 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 20 Sep 2024 12:53:11 +0000 Subject: [PATCH 655/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 7f3e8e8ed3..5b04fe2e46 100644 --- a/release-notes.md +++ b/release-notes.md @@ -16,6 +16,10 @@ * 🔥 Enable support for Arm Docker images in Mac, remove old patch. PR [#1341](https://github.com/fastapi/full-stack-fastapi-template/pull/1341) by [@tiangolo](https://github.com/tiangolo). * ♻️ Remove duplicate information in the ItemCreate model. PR [#1287](https://github.com/fastapi/full-stack-fastapi-template/pull/1287) by [@jjaakko](https://github.com/jjaakko). +### Upgrades + +* ⬆️ Upgrade FastAPI. PR [#1349](https://github.com/fastapi/full-stack-fastapi-template/pull/1349) by [@tiangolo](https://github.com/tiangolo). + ### Docs * 📝 Add Email Templates to `backend/README.md`. PR [#1311](https://github.com/fastapi/full-stack-fastapi-template/pull/1311) by [@alejsdev](https://github.com/alejsdev). From 034bd2fd6c16bca89d2a4f499a98bb1a606d1430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 20 Sep 2024 17:12:40 +0200 Subject: [PATCH 656/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20prestar?= =?UTF-8?q?t=20(migrations),=20move=20that=20to=20its=20own=20container=20?= =?UTF-8?q?(#1350)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Dockerfile | 2 ++ docker-compose.override.yml | 8 ++++++-- docker-compose.yml | 41 +++++++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index c3187aeb28..716aa231ad 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -26,3 +26,5 @@ COPY ./prestart.sh /app/ COPY ./tests-start.sh /app/ COPY ./app /app/app + +CMD ["fastapi", "run", "--workers", "4", "app/main.py"] diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 55965e4cf1..9257ab8aaa 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -59,7 +59,7 @@ services: restart: "no" ports: - "8888:8888" - - "8000:80" + - "8000:8000" volumes: - ./backend/:/app build: @@ -67,7 +67,11 @@ services: args: INSTALL_DEV: ${INSTALL_DEV-true} # command: sleep infinity # Infinite loop to keep container alive doing nothing - command: /start-reload.sh + command: + - fastapi + - run + - --reload + - "app/main.py" environment: SMTP_HOST: "mailcatcher" SMTP_PORT: "1025" diff --git a/docker-compose.yml b/docker-compose.yml index d65519135d..ce92e3e3d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,6 +42,41 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.tls.certresolver=le - traefik.http.services.${STACK_NAME?Variable not set}-adminer.loadbalancer.server.port=8080 + prestart: + image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' + build: + context: ./backend + args: + INSTALL_DEV: ${INSTALL_DEV-false} + networks: + - traefik-public + - default + depends_on: + db: + condition: service_healthy + restart: true + command: bash prestart.sh + env_file: + - .env + environment: + - DOMAIN=${DOMAIN} + - FRONTEND_HOST=${FRONTEND_HOST?Variable not set} + - ENVIRONMENT=${ENVIRONMENT} + - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS} + - SECRET_KEY=${SECRET_KEY?Variable not set} + - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set} + - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set} + - SMTP_HOST=${SMTP_HOST} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - EMAILS_FROM_EMAIL=${EMAILS_FROM_EMAIL} + - POSTGRES_SERVER=db + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER?Variable not set} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} + - SENTRY_DSN=${SENTRY_DSN} + backend: image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' restart: always @@ -52,6 +87,8 @@ services: db: condition: service_healthy restart: true + prestart: + condition: service_completed_successfully env_file: - .env environment: @@ -74,7 +111,7 @@ services: - SENTRY_DSN=${SENTRY_DSN} healthcheck: - test: ["CMD", "curl", "-f", "http://localhost/api/v1/utils/health-check/"] + test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/utils/health-check/"] interval: 10s timeout: 5s retries: 5 @@ -88,7 +125,7 @@ services: - traefik.docker.network=traefik-public - traefik.constraint-label=traefik-public - - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 + - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=8000 - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`api.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http From 7970dc97fa53dc03dba34e00b262e6f7d9336697 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 20 Sep 2024 15:13:01 +0000 Subject: [PATCH 657/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 5b04fe2e46..453cb2cb97 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* ♻️ Refactor prestart (migrations), move that to its own container. PR [#1350](https://github.com/fastapi/full-stack-fastapi-template/pull/1350) by [@tiangolo](https://github.com/tiangolo). * ♻️ Include `FRONTEND_HOST` in CORS origins by default. PR [#1348](https://github.com/fastapi/full-stack-fastapi-template/pull/1348) by [@tiangolo](https://github.com/tiangolo). * ♻️ Simplify domains with `api.example.com` for API and `dashboard.example.com` for frontend, improve local development with `localhost`. PR [#1344](https://github.com/fastapi/full-stack-fastapi-template/pull/1344) by [@tiangolo](https://github.com/tiangolo). * 🔥 Simplify Traefik, remove www-redirects that add complexity. PR [#1343](https://github.com/fastapi/full-stack-fastapi-template/pull/1343) by [@tiangolo](https://github.com/tiangolo). From 102f241b0d6a907cde36d9fac9df4b959eb0b18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Sep 2024 18:19:57 +0200 Subject: [PATCH 658/771] =?UTF-8?q?=F0=9F=9A=9A=20Move=20location=20of=20s?= =?UTF-8?q?cripts=20to=20simplify=20file=20structure=20(#1352)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 4 ++-- backend/Dockerfile | 6 +----- backend/README.md | 6 +++--- backend/{ => scripts}/prestart.sh | 4 ++-- backend/scripts/tests-start.sh | 7 +++++++ backend/tests-start.sh | 7 ------- docker-compose.yml | 2 +- scripts/test-local.sh | 2 +- scripts/test.sh | 2 +- 9 files changed, 18 insertions(+), 22 deletions(-) rename backend/{ => scripts}/prestart.sh (61%) create mode 100644 backend/scripts/tests-start.sh delete mode 100644 backend/tests-start.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5849d19749..1874f6886b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,9 +26,9 @@ jobs: - run: docker compose down -v --remove-orphans - run: docker compose up -d - name: Lint - run: docker compose exec -T backend bash /app/scripts/lint.sh + run: docker compose exec -T backend bash scripts/lint.sh - name: Run tests - run: docker compose exec -T backend bash /app/tests-start.sh "Coverage for ${{ github.sha }}" + run: docker compose exec -T backend bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" - run: docker compose down -v --remove-orphans - name: Store coverage files uses: actions/upload-artifact@v4 diff --git a/backend/Dockerfile b/backend/Dockerfile index 716aa231ad..29331dc712 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -17,14 +17,10 @@ RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; els ENV PYTHONPATH=/app -COPY ./scripts/ /app/ +COPY ./scripts /app/scripts COPY ./alembic.ini /app/ -COPY ./prestart.sh /app/ - -COPY ./tests-start.sh /app/ - COPY ./app /app/app CMD ["fastapi", "run", "--workers", "4", "app/main.py"] diff --git a/backend/README.md b/backend/README.md index e7782d43e6..b7a77bd48d 100644 --- a/backend/README.md +++ b/backend/README.md @@ -106,15 +106,15 @@ If you use GitHub Actions the tests will run automatically. If your stack is already up and you just want to run the tests, you can use: ```bash -docker compose exec backend bash /app/tests-start.sh +docker compose exec backend bash scripts/tests-start.sh ``` -That `/app/tests-start.sh` script just calls `pytest` after making sure that the rest of the stack is running. If you need to pass extra arguments to `pytest`, you can pass them to that command and they will be forwarded. +That `/app/scripts/tests-start.sh` script just calls `pytest` after making sure that the rest of the stack is running. If you need to pass extra arguments to `pytest`, you can pass them to that command and they will be forwarded. For example, to stop on first error: ```bash -docker compose exec backend bash /app/tests-start.sh -x +docker compose exec backend bash scripts/tests-start.sh -x ``` ### Test Coverage diff --git a/backend/prestart.sh b/backend/scripts/prestart.sh similarity index 61% rename from backend/prestart.sh rename to backend/scripts/prestart.sh index fc1e5f1890..ee1f5e252f 100644 --- a/backend/prestart.sh +++ b/backend/scripts/prestart.sh @@ -1,10 +1,10 @@ #! /usr/bin/env bash # Let the DB start -python /app/app/backend_pre_start.py +python app/backend_pre_start.py # Run migrations alembic upgrade head # Create initial data in DB -python /app/app/initial_data.py +python app/initial_data.py diff --git a/backend/scripts/tests-start.sh b/backend/scripts/tests-start.sh new file mode 100644 index 0000000000..89dcb0da23 --- /dev/null +++ b/backend/scripts/tests-start.sh @@ -0,0 +1,7 @@ +#! /usr/bin/env bash +set -e +set -x + +python app/tests_pre_start.py + +bash scripts/test.sh "$@" diff --git a/backend/tests-start.sh b/backend/tests-start.sh deleted file mode 100644 index e35150c7ae..0000000000 --- a/backend/tests-start.sh +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env bash -set -e -set -x - -python /app/app/tests_pre_start.py - -bash ./scripts/test.sh "$@" diff --git a/docker-compose.yml b/docker-compose.yml index ce92e3e3d7..db168b89cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,7 +55,7 @@ services: db: condition: service_healthy restart: true - command: bash prestart.sh + command: bash scripts/prestart.sh env_file: - .env environment: diff --git a/scripts/test-local.sh b/scripts/test-local.sh index 4c180f21d3..7f2fa9fbce 100644 --- a/scripts/test-local.sh +++ b/scripts/test-local.sh @@ -12,4 +12,4 @@ fi docker-compose build docker-compose up -d -docker-compose exec -T backend bash /app/tests-start.sh "$@" +docker-compose exec -T backend bash scripts/tests-start.sh "$@" diff --git a/scripts/test.sh b/scripts/test.sh index 73c449a993..6dabef7471 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -7,5 +7,5 @@ set -x docker compose build docker compose down -v --remove-orphans # Remove possibly previous broken stacks left hanging after an error docker compose up -d -docker compose exec -T backend bash /app/tests-start.sh "$@" +docker compose exec -T backend bash scripts/tests-start.sh "$@" docker compose down -v --remove-orphans From 301600237c6741e9967432247c34de72f8ca4c30 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 22 Sep 2024 16:20:17 +0000 Subject: [PATCH 659/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 453cb2cb97..a20669f823 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* 🚚 Move location of scripts to simplify file structure. PR [#1352](https://github.com/fastapi/full-stack-fastapi-template/pull/1352) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor prestart (migrations), move that to its own container. PR [#1350](https://github.com/fastapi/full-stack-fastapi-template/pull/1350) by [@tiangolo](https://github.com/tiangolo). * ♻️ Include `FRONTEND_HOST` in CORS origins by default. PR [#1348](https://github.com/fastapi/full-stack-fastapi-template/pull/1348) by [@tiangolo](https://github.com/tiangolo). * ♻️ Simplify domains with `api.example.com` for API and `dashboard.example.com` for frontend, improve local development with `localhost`. PR [#1344](https://github.com/fastapi/full-stack-fastapi-template/pull/1344) by [@tiangolo](https://github.com/tiangolo). From 77df4c20b2eaf62b6e68e9855f46c338f402cce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Sep 2024 18:25:29 +0200 Subject: [PATCH 660/771] =?UTF-8?q?=F0=9F=94=A7=20Use=20plain=20base=20off?= =?UTF-8?q?icial=20Python=20Docker=20image=20(#1351)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Dockerfile | 2 +- backend/README.md | 2 +- backend/scripts/prestart.sh | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 29331dc712..bb67c32a81 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.10 +FROM python:3.10 WORKDIR /app/ diff --git a/backend/README.md b/backend/README.md index b7a77bd48d..a8f18e9e5a 100644 --- a/backend/README.md +++ b/backend/README.md @@ -155,7 +155,7 @@ If you don't want to use migrations at all, uncomment the lines in the file at ` SQLModel.metadata.create_all(engine) ``` -and comment the line in the file `prestart.sh` that contains: +and comment the line in the file `scripts/prestart.sh` that contains: ```console $ alembic upgrade head diff --git a/backend/scripts/prestart.sh b/backend/scripts/prestart.sh index ee1f5e252f..1b395d513f 100644 --- a/backend/scripts/prestart.sh +++ b/backend/scripts/prestart.sh @@ -1,5 +1,8 @@ #! /usr/bin/env bash +set -e +set -x + # Let the DB start python app/backend_pre_start.py From 7632c2513dec6621aabc0930488dbe7d8af480a3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 22 Sep 2024 16:25:47 +0000 Subject: [PATCH 661/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index a20669f823..00a2d2a7c4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* 🔧 Use plain base official Python Docker image. PR [#1351](https://github.com/fastapi/full-stack-fastapi-template/pull/1351) by [@tiangolo](https://github.com/tiangolo). * 🚚 Move location of scripts to simplify file structure. PR [#1352](https://github.com/fastapi/full-stack-fastapi-template/pull/1352) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor prestart (migrations), move that to its own container. PR [#1350](https://github.com/fastapi/full-stack-fastapi-template/pull/1350) by [@tiangolo](https://github.com/tiangolo). * ♻️ Include `FRONTEND_HOST` in CORS origins by default. PR [#1348](https://github.com/fastapi/full-stack-fastapi-template/pull/1348) by [@tiangolo](https://github.com/tiangolo). From 1ea59e5dccd3e29ec3f3845aa3834255dc0aff45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Sep 2024 00:29:23 +0200 Subject: [PATCH 662/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20Docker=20Com?= =?UTF-8?q?pose=20`watch`=20(#1354)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/README.md | 16 ++++++++-------- development.md | 10 +++++----- docker-compose.override.yml | 15 +++++++++++++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/backend/README.md b/backend/README.md index a8f18e9e5a..e72ec444e7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -41,12 +41,12 @@ During development, you can change Docker Compose settings that will only affect The changes to that file only affect the local development environment, not the production environment. So, you can add "temporary" changes that help the development workflow. -For example, the directory with the backend code is mounted as a Docker "host volume", mapping the code you change live to the directory inside the container. That allows you to test your changes right away, without having to build the Docker image again. It should only be done during development, for production, you should build the Docker image with a recent version of the backend code. But during development, it allows you to iterate very fast. +For example, the directory with the backend code is synchronized in the Docker container, copying the code you change live to the directory inside the container. That allows you to test your changes right away, without having to build the Docker image again. It should only be done during development, for production, you should build the Docker image with a recent version of the backend code. But during development, it allows you to iterate very fast. -There is also a command override that runs `/start-reload.sh` (included in the base image) instead of the default `/start.sh` (also included in the base image). It starts a single server process (instead of multiple, as would be for production) and reloads the process whenever the code changes. Have in mind that if you have a syntax error and save the Python file, it will break and exit, and the container will stop. After that, you can restart the container by fixing the error and running again: +There is also a command override that runs `fastapi run --reload` instead of the default `fastapi run`. It starts a single server process (instead of multiple, as would be for production) and reloads the process whenever the code changes. Have in mind that if you have a syntax error and save the Python file, it will break and exit, and the container will stop. After that, you can restart the container by fixing the error and running again: ```console -$ docker compose up -d +$ docker compose watch ``` There is also a commented out `command` override, you can uncomment it and comment the default one. It makes the backend container run a process that does "nothing", but keeps the container alive. That allows you to get inside your running container and execute commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes. @@ -54,10 +54,10 @@ There is also a commented out `command` override, you can uncomment it and comme To get inside the container with a `bash` session you can start the stack with: ```console -$ docker compose up -d +$ docker compose watch ``` -and then `exec` inside the running container: +and then in another terminal, `exec` inside the running container: ```console $ docker compose exec backend bash @@ -71,16 +71,16 @@ root@7f2607af31c3:/app# that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory, this directory has another directory called "app" inside, that's where your code lives inside the container: `/app/app`. -There you can use the script `/start-reload.sh` to run the debug live reloading server. You can run that script from inside the container with: +There you can use the `fastapi run --reload` command to run the debug live reloading server. ```console -$ bash /start-reload.sh +$ fastapi run --reload app/main.py ``` ...it will look like: ```console -root@7f2607af31c3:/app# bash /start-reload.sh +root@7f2607af31c3:/app# fastapi run --reload app/main.py ``` and then hit enter. That runs the live reloading server that auto reloads when it detects code changes. diff --git a/development.md b/development.md index 38d17eb2cc..ad1cd75cbc 100644 --- a/development.md +++ b/development.md @@ -5,7 +5,7 @@ * Start the local stack with Docker Compose: ```bash -docker compose up -d +docker compose watch ``` * Now you can open your browser and interact with these URLs: @@ -22,7 +22,7 @@ Traefik UI, to see how the routes are being handled by the proxy: http://localho **Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it. -To check the logs, run: +To check the logs, run (in another terminal): ```bash docker compose logs @@ -42,7 +42,7 @@ For the backend and frontend, they use the same port that would be used by their This way, you could turn off a Docker Compose service and start its local development service, and everything would keep working, because it all uses the same ports. -For example, you can stop that `frontend` service in the Docker Compose: +For example, you can stop that `frontend` service in the Docker Compose, in another terminal, run: ```bash docker compose stop frontend @@ -91,7 +91,7 @@ The domain `localhost.tiangolo.com` is a special domain that is configured (with After you update it, run again: ```bash -docker compose up -d +docker compose watch ``` When deploying, for example in production, the main Traefik is configured outside of the Docker Compose files. For local development, there's an included Traefik in `docker-compose.override.yml`, just to let you test that the domains work as expected, for example with `api.localhost.tiangolo.com` and `dashboard.localhost.tiangolo.com`. @@ -109,7 +109,7 @@ They also use some additional configurations taken from environment variables se After changing variables, make sure you restart the stack: ```bash -docker compose up -d +docker compose watch ``` ## The .env file diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 9257ab8aaa..fd99cb538c 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -60,8 +60,6 @@ services: ports: - "8888:8888" - "8000:8000" - volumes: - - ./backend/:/app build: context: ./backend args: @@ -72,6 +70,19 @@ services: - run - --reload - "app/main.py" + develop: + watch: + - path: ./backend + action: sync + target: /app + ignore: + - ./backend/.venv + - .venv + - path: ./backend/pyproject.toml + action: rebuild + # TODO: remove once coverage is done locally + volumes: + - ./backend/htmlcov:/app/htmlcov environment: SMTP_HOST: "mailcatcher" SMTP_PORT: "1025" From b113d278e9ba06b10c2d9d065c5e6992f53e709c Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 22 Sep 2024 22:29:44 +0000 Subject: [PATCH 663/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 00a2d2a7c4..a269cb2a9b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* ♻️ Use Docker Compose `watch`. PR [#1354](https://github.com/fastapi/full-stack-fastapi-template/pull/1354) by [@tiangolo](https://github.com/tiangolo). * 🔧 Use plain base official Python Docker image. PR [#1351](https://github.com/fastapi/full-stack-fastapi-template/pull/1351) by [@tiangolo](https://github.com/tiangolo). * 🚚 Move location of scripts to simplify file structure. PR [#1352](https://github.com/fastapi/full-stack-fastapi-template/pull/1352) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor prestart (migrations), move that to its own container. PR [#1350](https://github.com/fastapi/full-stack-fastapi-template/pull/1350) by [@tiangolo](https://github.com/tiangolo). From fdac322edebba54693d10370c6a8ccce7017950c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Sep 2024 13:39:54 +0200 Subject: [PATCH 664/771] =?UTF-8?q?=F0=9F=94=A5=20Remove=20logic=20for=20d?= =?UTF-8?q?evelopment=20dependencies=20and=20Jupyter,=20it=20was=20never?= =?UTF-8?q?=20documented,=20and=20I=20no=20longer=20use=20that=20trick=20(?= =?UTF-8?q?#1355)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Dockerfile | 4 +--- docker-compose.override.yml | 3 --- docker-compose.yml | 4 ---- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index bb67c32a81..223b9317ab 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -11,9 +11,7 @@ RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python # Copy poetry.lock* in case it doesn't exist in the repo COPY ./pyproject.toml ./poetry.lock* /app/ -# Allow installing dev dependencies to run tests -ARG INSTALL_DEV=false -RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; else poetry install --no-root --only main ; fi" +RUN poetry install --no-root ENV PYTHONPATH=/app diff --git a/docker-compose.override.yml b/docker-compose.override.yml index fd99cb538c..3792f1f026 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -58,12 +58,9 @@ services: backend: restart: "no" ports: - - "8888:8888" - "8000:8000" build: context: ./backend - args: - INSTALL_DEV: ${INSTALL_DEV-true} # command: sleep infinity # Infinite loop to keep container alive doing nothing command: - fastapi diff --git a/docker-compose.yml b/docker-compose.yml index db168b89cb..c92d5d4451 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,8 +46,6 @@ services: image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' build: context: ./backend - args: - INSTALL_DEV: ${INSTALL_DEV-false} networks: - traefik-public - default @@ -118,8 +116,6 @@ services: build: context: ./backend - args: - INSTALL_DEV: ${INSTALL_DEV-false} labels: - traefik.enable=true - traefik.docker.network=traefik-public From e7bee72149db087ef1eb66f461e720eac40c178a Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 11:40:12 +0000 Subject: [PATCH 665/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index a269cb2a9b..32b4894603 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* 🔥 Remove logic for development dependencies and Jupyter, it was never documented, and I no longer use that trick. PR [#1355](https://github.com/fastapi/full-stack-fastapi-template/pull/1355) by [@tiangolo](https://github.com/tiangolo). * ♻️ Use Docker Compose `watch`. PR [#1354](https://github.com/fastapi/full-stack-fastapi-template/pull/1354) by [@tiangolo](https://github.com/tiangolo). * 🔧 Use plain base official Python Docker image. PR [#1351](https://github.com/fastapi/full-stack-fastapi-template/pull/1351) by [@tiangolo](https://github.com/tiangolo). * 🚚 Move location of scripts to simplify file structure. PR [#1352](https://github.com/fastapi/full-stack-fastapi-template/pull/1352) by [@tiangolo](https://github.com/tiangolo). From cdf2198544d002f1d9a37e5388404bffac5f579f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Sep 2024 16:10:46 +0200 Subject: [PATCH 666/771] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Migrate=20from=20P?= =?UTF-8?q?oetry=20to=20uv=20(#1356)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Dockerfile | 32 +- backend/README.md | 12 +- backend/poetry.lock | 2204 ---------------------------------------- backend/pyproject.toml | 65 +- backend/uv.lock | 1554 ++++++++++++++++++++++++++++ copier.yml | 2 - development.md | 8 +- 7 files changed, 1619 insertions(+), 2258 deletions(-) delete mode 100644 backend/poetry.lock create mode 100644 backend/uv.lock diff --git a/backend/Dockerfile b/backend/Dockerfile index 223b9317ab..08f21fcc66 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -2,23 +2,37 @@ FROM python:3.10 WORKDIR /app/ -# Install Poetry -RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \ - cd /usr/local/bin && \ - ln -s /opt/poetry/bin/poetry && \ - poetry config virtualenvs.create false +# Install uv +# Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv +COPY --from=ghcr.io/astral-sh/uv:0.4.15 /uv /bin/uv -# Copy poetry.lock* in case it doesn't exist in the repo -COPY ./pyproject.toml ./poetry.lock* /app/ +# Place executables in the environment at the front of the path +# Ref: https://docs.astral.sh/uv/guides/integration/docker/#using-the-environment +ENV PATH="/app/.venv/bin:$PATH" -RUN poetry install --no-root +# Compile bytecode +# Ref: https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode +ENV UV_COMPILE_BYTECODE=1 + +# uv Cache +# Ref: https://docs.astral.sh/uv/guides/integration/docker/#caching +ENV UV_LINK_MODE=copy + +# Install dependencies +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project ENV PYTHONPATH=/app COPY ./scripts /app/scripts -COPY ./alembic.ini /app/ +COPY ./pyproject.toml ./uv.lock ./alembic.ini /app/ COPY ./app /app/app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync + CMD ["fastapi", "run", "--workers", "4", "app/main.py"] diff --git a/backend/README.md b/backend/README.md index e72ec444e7..17210a2f2c 100644 --- a/backend/README.md +++ b/backend/README.md @@ -3,7 +3,7 @@ ## Requirements * [Docker](https://www.docker.com/). -* [Poetry](https://python-poetry.org/) for Python package and environment management. +* [uv](https://docs.astral.sh/uv/) for Python package and environment management. ## Docker Compose @@ -11,21 +11,21 @@ Start the local development environment with Docker Compose following the guide ## General Workflow -By default, the dependencies are managed with [Poetry](https://python-poetry.org/), go there and install it. +By default, the dependencies are managed with [uv](https://docs.astral.sh/uv/), go there and install it. From `./backend/` you can install all the dependencies with: ```console -$ poetry install +$ uv sync ``` -Then you can start a shell session with the new environment with: +Then you can activate the virtual environment with: ```console -$ poetry shell +$ source .venv/bin/activate ``` -Make sure your editor is using the correct Python virtual environment. +Make sure your editor is using the correct Python virtual environment, with the interpreter at `backend/.venv/bin/python`. Modify or add SQLModel models for data and SQL tables in `./backend/app/models.py`, API endpoints in `./backend/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/crud.py`. diff --git a/backend/poetry.lock b/backend/poetry.lock deleted file mode 100644 index 4ba8fba010..0000000000 --- a/backend/poetry.lock +++ /dev/null @@ -1,2204 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "alembic" -version = "1.13.2" -description = "A database migration tool for SQLAlchemy." -optional = false -python-versions = ">=3.8" -files = [ - {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, - {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, -] - -[package.dependencies] -Mako = "*" -SQLAlchemy = ">=1.3.0" -typing-extensions = ">=4" - -[package.extras] -tz = ["backports.zoneinfo"] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.4.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "bcrypt" -version = "4.0.1" -description = "Modern password hashing for your software and your servers" -optional = false -python-versions = ">=3.6" -files = [ - {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, - {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, - {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, - {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, - {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, - {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, -] - -[package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] -typecheck = ["mypy"] - -[[package]] -name = "cachetools" -version = "5.4.0" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -files = [ - {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, - {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, -] - -[[package]] -name = "certifi" -version = "2024.7.4" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "chardet" -version = "5.2.0" -description = "Universal encoding detector for Python 3" -optional = false -python-versions = ">=3.7" -files = [ - {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, - {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.6.0" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, - {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, - {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, - {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, - {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, - {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, - {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, - {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, - {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, - {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, - {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, - {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, - {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, - {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, -] - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "cssselect" -version = "1.2.0" -description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" -optional = false -python-versions = ">=3.7" -files = [ - {file = "cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"}, - {file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"}, -] - -[[package]] -name = "cssutils" -version = "2.11.1" -description = "A CSS Cascading Style Sheets library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1"}, - {file = "cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2"}, -] - -[package.dependencies] -more-itertools = "*" - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - -[[package]] -name = "email-validator" -version = "2.2.0" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, - {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - -[[package]] -name = "emails" -version = "0.6" -description = "Modern python library for emails." -optional = false -python-versions = "*" -files = [ - {file = "emails-0.6-py2.py3-none-any.whl", hash = "sha256:72c1e3198075709cc35f67e1b49e2da1a2bc087e9b444073db61a379adfb7f3c"}, - {file = "emails-0.6.tar.gz", hash = "sha256:a4c2d67ea8b8831967a750d8edc6e77040d7693143fe280e6d2a367d9c36ff88"}, -] - -[package.dependencies] -chardet = "*" -cssutils = "*" -lxml = "*" -premailer = "*" -python-dateutil = "*" -requests = "*" - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "fastapi" -version = "0.114.2" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi-0.114.2-py3-none-any.whl", hash = "sha256:44474a22913057b1acb973ab90f4b671ba5200482e7622816d79105dcece1ac5"}, - {file = "fastapi-0.114.2.tar.gz", hash = "sha256:0adb148b62edb09e8c6eeefa3ea934e8f276dabc038c5a82989ea6346050c3da"}, -] - -[package.dependencies] -email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"standard\""} -fastapi-cli = {version = ">=0.0.5", extras = ["standard"], optional = true, markers = "extra == \"standard\""} -httpx = {version = ">=0.23.0", optional = true, markers = "extra == \"standard\""} -jinja2 = {version = ">=2.11.2", optional = true, markers = "extra == \"standard\""} -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -python-multipart = {version = ">=0.0.7", optional = true, markers = "extra == \"standard\""} -starlette = ">=0.37.2,<0.39.0" -typing-extensions = ">=4.8.0" -uvicorn = {version = ">=0.12.0", extras = ["standard"], optional = true, markers = "extra == \"standard\""} - -[package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "fastapi-cli" -version = "0.0.5" -description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi_cli-0.0.5-py3-none-any.whl", hash = "sha256:e94d847524648c748a5350673546bbf9bcaeb086b33c24f2e82e021436866a46"}, - {file = "fastapi_cli-0.0.5.tar.gz", hash = "sha256:d30e1239c6f46fcb95e606f02cdda59a1e2fa778a54b64686b3ff27f6211ff9f"}, -] - -[package.dependencies] -typer = ">=0.12.3" -uvicorn = {version = ">=0.15.0", extras = ["standard"]} - -[package.extras] -standard = ["uvicorn[standard] (>=0.15.0)"] - -[[package]] -name = "filelock" -version = "3.15.4" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "gunicorn" -version = "22.0.0" -description = "WSGI HTTP Server for UNIX" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, - {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, -] - -[package.dependencies] -packaging = "*" - -[package.extras] -eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] -gevent = ["gevent (>=1.4.0)"] -setproctitle = ["setproctitle"] -testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] -tornado = ["tornado (>=0.2)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.5" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] - -[[package]] -name = "httptools" -version = "0.6.1" -description = "A collection of framework independent HTTP protocol utils." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - -[[package]] -name = "httpx" -version = "0.25.2" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.25.2-py3-none-any.whl", hash = "sha256:a05d3d052d9b2dfce0e3896636467f8a5342fb2b902c819428e1ac65413ca118"}, - {file = "httpx-0.25.2.tar.gz", hash = "sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] - -[[package]] -name = "identify" -version = "2.6.0" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, - {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "lxml" -version = "5.2.2" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -optional = false -python-versions = ">=3.6" -files = [ - {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632"}, - {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f"}, - {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b"}, - {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835"}, - {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0"}, - {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c"}, - {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316"}, - {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0"}, - {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b"}, - {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393"}, - {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526"}, - {file = "lxml-5.2.2-cp310-cp310-win32.whl", hash = "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30"}, - {file = "lxml-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7"}, - {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545"}, - {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d"}, - {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"}, - {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab"}, - {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115"}, - {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04"}, - {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad"}, - {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8"}, - {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5"}, - {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa"}, - {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b"}, - {file = "lxml-5.2.2-cp311-cp311-win32.whl", hash = "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438"}, - {file = "lxml-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be"}, - {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391"}, - {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466"}, - {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927"}, - {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf"}, - {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf"}, - {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67"}, - {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d"}, - {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585"}, - {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe"}, - {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c"}, - {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836"}, - {file = "lxml-5.2.2-cp312-cp312-win32.whl", hash = "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a"}, - {file = "lxml-5.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48"}, - {file = "lxml-5.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8"}, - {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706"}, - {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573"}, - {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce"}, - {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56"}, - {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9"}, - {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264"}, - {file = "lxml-5.2.2-cp36-cp36m-win32.whl", hash = "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3"}, - {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, - {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, - {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, - {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, - {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, - {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, - {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, - {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, - {file = "lxml-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3"}, - {file = "lxml-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421"}, - {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706"}, - {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f"}, - {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5"}, - {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48"}, - {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66"}, - {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3"}, - {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b"}, - {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1"}, - {file = "lxml-5.2.2-cp38-cp38-win32.whl", hash = "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30"}, - {file = "lxml-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6"}, - {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30"}, - {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a"}, - {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab"}, - {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d"}, - {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981"}, - {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32"}, - {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3"}, - {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207"}, - {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d"}, - {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472"}, - {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9"}, - {file = "lxml-5.2.2-cp39-cp39-win32.whl", hash = "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf"}, - {file = "lxml-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425"}, - {file = "lxml-5.2.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2"}, - {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9"}, - {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae"}, - {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1"}, - {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2"}, - {file = "lxml-5.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b"}, - {file = "lxml-5.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b"}, - {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009"}, - {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07"}, - {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484"}, - {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8"}, - {file = "lxml-5.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b"}, - {file = "lxml-5.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525"}, - {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34"}, - {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6"}, - {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14"}, - {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db"}, - {file = "lxml-5.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511"}, - {file = "lxml-5.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d"}, - {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0"}, - {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e"}, - {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182"}, - {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a"}, - {file = "lxml-5.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324"}, - {file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"}, -] - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html-clean = ["lxml-html-clean"] -html5 = ["html5lib"] -htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.10)"] - -[[package]] -name = "mako" -version = "1.3.5" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, - {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, -] - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "more-itertools" -version = "10.3.0" -description = "More routines for operating on iterables, beyond itertools" -optional = false -python-versions = ">=3.8" -files = [ - {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, - {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, -] - -[[package]] -name = "mypy" -version = "1.11.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, - {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, - {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, - {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, - {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, - {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, - {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, - {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, - {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, - {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, - {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, - {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, - {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, - {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, - {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, - {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, - {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, - {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, - {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "passlib" -version = "1.7.4" -description = "comprehensive password hashing framework supporting over 30 schemes" -optional = false -python-versions = "*" -files = [ - {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, - {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, -] - -[package.dependencies] -bcrypt = {version = ">=3.1.0", optional = true, markers = "extra == \"bcrypt\""} - -[package.extras] -argon2 = ["argon2-cffi (>=18.2.0)"] -bcrypt = ["bcrypt (>=3.1.0)"] -build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] -totp = ["cryptography"] - -[[package]] -name = "platformdirs" -version = "4.2.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "3.8.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "premailer" -version = "3.10.0" -description = "Turns CSS blocks into style attributes" -optional = false -python-versions = "*" -files = [ - {file = "premailer-3.10.0-py2.py3-none-any.whl", hash = "sha256:021b8196364d7df96d04f9ade51b794d0b77bcc19e998321c515633a2273be1a"}, - {file = "premailer-3.10.0.tar.gz", hash = "sha256:d1875a8411f5dc92b53ef9f193db6c0f879dc378d618e0ad292723e388bfe4c2"}, -] - -[package.dependencies] -cachetools = "*" -cssselect = "*" -cssutils = "*" -lxml = "*" -requests = "*" - -[package.extras] -dev = ["black", "flake8", "therapist", "tox", "twine", "wheel"] -test = ["mock", "nose"] - -[[package]] -name = "psycopg" -version = "3.2.1" -description = "PostgreSQL database adapter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"}, - {file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"}, -] - -[package.dependencies] -psycopg-binary = {version = "3.2.1", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} -typing-extensions = ">=4.4" -tzdata = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -binary = ["psycopg-binary (==3.2.1)"] -c = ["psycopg-c (==3.2.1)"] -dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.6)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] -docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] -pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.6)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] - -[[package]] -name = "psycopg-binary" -version = "3.2.1" -description = "PostgreSQL database adapter for Python -- C optimisation distribution" -optional = false -python-versions = ">=3.8" -files = [ - {file = "psycopg_binary-3.2.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:cad2de17804c4cfee8640ae2b279d616bb9e4734ac3c17c13db5e40982bd710d"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:592b27d6c46a40f9eeaaeea7c1fef6f3c60b02c634365eb649b2d880669f149f"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a997efbaadb5e1a294fb5760e2f5643d7b8e4e3fe6cb6f09e6d605fd28e0291"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1d2b6438fb83376f43ebb798bf0ad5e57bc56c03c9c29c85bc15405c8c0ac5a"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1f087bd84bdcac78bf9f024ebdbfacd07fc0a23ec8191448a50679e2ac4a19e"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:415c3b72ea32119163255c6504085f374e47ae7345f14bc3f0ef1f6e0976a879"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f092114f10f81fb6bae544a0ec027eb720e2d9c74a4fcdaa9dd3899873136935"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06a7aae34edfe179ddc04da005e083ff6c6b0020000399a2cbf0a7121a8a22ea"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b018631e5c80ce9bc210b71ea885932f9cca6db131e4df505653d7e3873a938"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8a509aeaac364fa965454e80cd110fe6d48ba2c80f56c9b8563423f0b5c3cfd"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:413977d18412ff83486eeb5875eb00b185a9391c57febac45b8993bf9c0ff489"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:62b1b7b07e00ee490afb39c0a47d8282a9c2822c7cfed9553a04b0058adf7e7f"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8afb07114ea9b924a4a0305ceb15354ccf0ef3c0e14d54b8dbeb03e50182dd7"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40bb515d042f6a345714ec0403df68ccf13f73b05e567837d80c886c7c9d3805"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6418712ba63cebb0c88c050b3997185b0ef54173b36568522d5634ac06153040"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:101472468d59c74bb8565fab603e032803fd533d16be4b2d13da1bab8deb32a3"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa3931f308ab4a479d0ee22dc04bea867a6365cac0172e5ddcba359da043854b"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc314a47d44fe1a8069b075a64abffad347a3a1d8652fed1bab5d3baea37acb2"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc304a46be1e291031148d9d95c12451ffe783ff0cc72f18e2cc7ec43cdb8c68"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f9e13600647087df5928875559f0eb8f496f53e6278b7da9511b4b3d0aff960"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b140182830c76c74d17eba27df3755a46442ce8d4fb299e7f1cf2f74a87c877b"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:3c838806eeb99af39f934b7999e35f947a8e577997cc892c12b5053a97a9057f"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7066d3dca196ed0dc6172f9777b2d62e4f138705886be656cccff2d555234d60"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:28ada5f610468c57d8a4a055a8ea915d0085a43d794266c4f3b9d02f4288f4db"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e8213bf50af073b1aa8dc3cff123bfeedac86332a16c1b7274910bc88a847c7"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74d623261655a169bc84a9669890975c229f2fa6e19a7f2d10a77675dcf1a707"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42781ba94e8842ee98bca5a7d0c44cc9d067500fedca2d6a90fa3609b6d16b42"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e6669091d09f8ba36e10ce678a6d9916e110446236a9b92346464a3565635e"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b09e8a576a2ac69d695032ee76f31e03b30781828b5dd6d18c6a009e5a3d1c35"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8f28ff0cb9f1defdc4a6f8c958bf6787274247e7dfeca811f6e2f56602695fb1"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4c84fcac8a3a3479ac14673095cc4e1fdba2935499f72c436785ac679bec0d1a"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:950fd666ec9e9fe6a8eeb2b5a8f17301790e518953730ad44d715b59ffdbc67f"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:334046a937bb086c36e2c6889fe327f9f29bfc085d678f70fac0b0618949f674"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:1d6833f607f3fc7b22226a9e121235d3b84c0eda1d3caab174673ef698f63788"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d353e028b8f848b9784450fc2abf149d53a738d451eab3ee4c85703438128b9"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34e369891f77d0738e5d25727c307d06d5344948771e5379ea29c76c6d84555"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ab58213cc976a1666f66bc1cb2e602315cd753b7981a8e17237ac2a185bd4a1"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0104a72a17aa84b3b7dcab6c84826c595355bf54bb6ea6d284dcb06d99c6801"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:059cbd4e6da2337e17707178fe49464ed01de867dc86c677b30751755ec1dc51"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:73f9c9b984be9c322b5ec1515b12df1ee5896029f5e72d46160eb6517438659c"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:af0469c00f24c4bec18c3d2ede124bf62688d88d1b8a5f3c3edc2f61046fe0d7"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:463d55345f73ff391df8177a185ad57b552915ad33f5cc2b31b930500c068b22"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:302b86f92c0d76e99fe1b5c22c492ae519ce8b98b88d37ef74fda4c9e24c6b46"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0879b5d76b7d48678d31278242aaf951bc2d69ca4e4d7cef117e4bbf7bfefda9"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99e59f8a5f4dcd9cbdec445f3d8ac950a492fc0e211032384d6992ed3c17eb7"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84837e99353d16c6980603b362d0f03302d4b06c71672a6651f38df8a482923d"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ce965caf618061817f66c0906f0452aef966c293ae0933d4fa5a16ea6eaf5bb"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78c2007caf3c90f08685c5378e3ceb142bafd5636be7495f7d86ec8a977eaeef"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7a84b5eb194a258116154b2a4ff2962ea60ea52de089508db23a51d3d6b1c7d1"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4a42b8f9ab39affcd5249b45cac763ac3cf12df962b67e23fd15a2ee2932afe5"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:788ffc43d7517c13e624c83e0e553b7b8823c9655e18296566d36a829bfb373f"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:21927f41c4d722ae8eb30d62a6ce732c398eac230509af5ba1749a337f8a63e2"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:921f0c7f39590763d64a619de84d1b142587acc70fd11cbb5ba8fa39786f3073"}, -] - -[[package]] -name = "pydantic" -version = "2.8.2" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.20.1" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pydantic-settings" -version = "2.4.0" -description = "Settings management using Pydantic" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_settings-2.4.0-py3-none-any.whl", hash = "sha256:bb6849dc067f1687574c12a639e231f3a6feeed0a12d710c1382045c5db1c315"}, - {file = "pydantic_settings-2.4.0.tar.gz", hash = "sha256:ed81c3a0f46392b4d7c0a565c05884e6e54b3456e6f0fe4d8814981172dc9a88"}, -] - -[package.dependencies] -pydantic = ">=2.7.0" -python-dotenv = ">=0.21.0" - -[package.extras] -azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] -toml = ["tomli (>=2.0.1)"] -yaml = ["pyyaml (>=6.0.1)"] - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[package.extras] -crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "python-multipart" -version = "0.0.7" -description = "A streaming multipart parser for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "python_multipart-0.0.7-py3-none-any.whl", hash = "sha256:b1fef9a53b74c795e2347daac8c54b252d9e0df9c619712691c1cc8021bd3c49"}, - {file = "python_multipart-0.0.7.tar.gz", hash = "sha256:288a6c39b06596c1b988bb6794c6fbc80e6c369e35e5062637df256bee0c9af9"}, -] - -[package.extras] -dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==2.2.0)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "13.8.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, - {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "ruff" -version = "0.2.2" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, - {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, - {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, - {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, - {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, - {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, -] - -[[package]] -name = "sentry-sdk" -version = "1.45.1" -description = "Python client for Sentry (https://sentry.io)" -optional = false -python-versions = "*" -files = [ - {file = "sentry_sdk-1.45.1-py2.py3-none-any.whl", hash = "sha256:608887855ccfe39032bfd03936e3a1c4f4fc99b3a4ac49ced54a4220de61c9c1"}, - {file = "sentry_sdk-1.45.1.tar.gz", hash = "sha256:a16c997c0f4e3df63c0fc5e4207ccb1ab37900433e0f72fef88315d317829a26"}, -] - -[package.dependencies] -certifi = "*" -fastapi = {version = ">=0.79.0", optional = true, markers = "extra == \"fastapi\""} -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} - -[package.extras] -aiohttp = ["aiohttp (>=3.5)"] -arq = ["arq (>=0.23)"] -asyncpg = ["asyncpg (>=0.23)"] -beam = ["apache-beam (>=2.12)"] -bottle = ["bottle (>=0.12.13)"] -celery = ["celery (>=3)"] -celery-redbeat = ["celery-redbeat (>=2)"] -chalice = ["chalice (>=1.16.0)"] -clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] -django = ["django (>=1.8)"] -falcon = ["falcon (>=1.4)"] -fastapi = ["fastapi (>=0.79.0)"] -flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] -httpx = ["httpx (>=0.16.0)"] -huey = ["huey (>=2)"] -loguru = ["loguru (>=0.5)"] -openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] -opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure-eval"] -pymongo = ["pymongo (>=3.1)"] -pyspark = ["pyspark (>=2.4.4)"] -quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] -rq = ["rq (>=0.6)"] -sanic = ["sanic (>=0.8)"] -sqlalchemy = ["sqlalchemy (>=1.2)"] -starlette = ["starlette (>=0.19.1)"] -starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.31" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, - {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, - {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "sqlmodel" -version = "0.0.21" -description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." -optional = false -python-versions = ">=3.7" -files = [ - {file = "sqlmodel-0.0.21-py3-none-any.whl", hash = "sha256:eca104afe8a643f0764076b29f02e51d19d6b35c458f4c119942960362a4b52a"}, - {file = "sqlmodel-0.0.21.tar.gz", hash = "sha256:b2034c23d930f66d2091b17a4280a9c23a7ea540a71e7fcf9c746d262f06f74a"}, -] - -[package.dependencies] -pydantic = ">=1.10.13,<3.0.0" -SQLAlchemy = ">=2.0.14,<2.1.0" - -[[package]] -name = "starlette" -version = "0.38.5" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.8" -files = [ - {file = "starlette-0.38.5-py3-none-any.whl", hash = "sha256:632f420a9d13e3ee2a6f18f437b0a9f1faecb0bc42e1942aa2ea0e379a4c4206"}, - {file = "starlette-0.38.5.tar.gz", hash = "sha256:04a92830a9b6eb1442c766199d62260c3d4dc9c4f9188360626b1e0273cb7077"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] - -[[package]] -name = "tenacity" -version = "8.5.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, - {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "typer" -version = "0.12.5" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -files = [ - {file = "typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b"}, - {file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722"}, -] - -[package.dependencies] -click = ">=8.0.0" -rich = ">=10.11.0" -shellingham = ">=1.3.0" -typing-extensions = ">=3.7.4.3" - -[[package]] -name = "types-passlib" -version = "1.7.7.20240327" -description = "Typing stubs for passlib" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-passlib-1.7.7.20240327.tar.gz", hash = "sha256:4cce6a1a3a6afee9fc4728b4d9784300764ac2be747f5bcc01646d904b85f4bb"}, - {file = "types_passlib-1.7.7.20240327-py3-none-any.whl", hash = "sha256:3a3b7f4258b71034d2e2f4f307d6810f9904f906cdf375514c8bdbdb28a4ad23"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - -[[package]] -name = "urllib3" -version = "2.2.2" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "uvicorn" -version = "0.24.0.post1" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.8" -files = [ - {file = "uvicorn-0.24.0.post1-py3-none-any.whl", hash = "sha256:7c84fea70c619d4a710153482c0d230929af7bcf76c7bfa6de151f0a3a80121e"}, - {file = "uvicorn-0.24.0.post1.tar.gz", hash = "sha256:09c8e5a79dc466bdf28dead50093957db184de356fcdc48697bad3bde4c2588e"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} -h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} -python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} -watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "uvloop" -version = "0.19.0" -description = "Fast implementation of asyncio event loop on top of libuv" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, -] - -[package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - -[[package]] -name = "virtualenv" -version = "20.26.3" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "watchfiles" -version = "0.22.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"}, - {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"}, - {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"}, - {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"}, - {file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"}, - {file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"}, - {file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"}, - {file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"}, - {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"}, - {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"}, - {file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"}, - {file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"}, - {file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"}, - {file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"}, - {file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"}, - {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"}, - {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"}, - {file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"}, - {file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"}, - {file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"}, - {file = "watchfiles-0.22.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d47e9ef1a94cc7a536039e46738e17cce058ac1593b2eccdede8bf72e45f372a"}, - {file = "watchfiles-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28f393c1194b6eaadcdd8f941307fc9bbd7eb567995232c830f6aef38e8a6e88"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd64f3a4db121bc161644c9e10a9acdb836853155a108c2446db2f5ae1778c3d"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2abeb79209630da981f8ebca30a2c84b4c3516a214451bfc5f106723c5f45843"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cc382083afba7918e32d5ef12321421ef43d685b9a67cc452a6e6e18920890e"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d048ad5d25b363ba1d19f92dcf29023988524bee6f9d952130b316c5802069cb"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:103622865599f8082f03af4214eaff90e2426edff5e8522c8f9e93dc17caee13"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e1f3cf81f1f823e7874ae563457828e940d75573c8fbf0ee66818c8b6a9099"}, - {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8597b6f9dc410bdafc8bb362dac1cbc9b4684a8310e16b1ff5eee8725d13dcd6"}, - {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b04a2cbc30e110303baa6d3ddce8ca3664bc3403be0f0ad513d1843a41c97d1"}, - {file = "watchfiles-0.22.0-cp38-none-win32.whl", hash = "sha256:b610fb5e27825b570554d01cec427b6620ce9bd21ff8ab775fc3a32f28bba63e"}, - {file = "watchfiles-0.22.0-cp38-none-win_amd64.whl", hash = "sha256:fe82d13461418ca5e5a808a9e40f79c1879351fcaeddbede094028e74d836e86"}, - {file = "watchfiles-0.22.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3973145235a38f73c61474d56ad6199124e7488822f3a4fc97c72009751ae3b0"}, - {file = "watchfiles-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:280a4afbc607cdfc9571b9904b03a478fc9f08bbeec382d648181c695648202f"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d883351a34c01bd53cfa75cd0292e3f7e268bacf2f9e33af4ecede7e21d1d"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9165bcab15f2b6d90eedc5c20a7f8a03156b3773e5fb06a790b54ccecdb73385"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc1b9b56f051209be458b87edb6856a449ad3f803315d87b2da4c93b43a6fe72"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc1fc25a1dedf2dd952909c8e5cb210791e5f2d9bc5e0e8ebc28dd42fed7562"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92d2d2706d2b862ce0568b24987eba51e17e14b79a1abcd2edc39e48e743c8"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b94e14b88409c58cdf4a8eaf0e67dfd3ece7e9ce7140ea6ff48b0407a593ec"}, - {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96eec15e5ea7c0b6eb5bfffe990fc7c6bd833acf7e26704eb18387fb2f5fd087"}, - {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:28324d6b28bcb8d7c1041648d7b63be07a16db5510bea923fc80b91a2a6cbed6"}, - {file = "watchfiles-0.22.0-cp39-none-win32.whl", hash = "sha256:8c3e3675e6e39dc59b8fe5c914a19d30029e36e9f99468dddffd432d8a7b1c93"}, - {file = "watchfiles-0.22.0-cp39-none-win_amd64.whl", hash = "sha256:25c817ff2a86bc3de3ed2df1703e3d24ce03479b27bb4527c57e722f8554d971"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"}, - {file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - -[[package]] -name = "websockets" -version = "12.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, -] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "62870188540a73228ca7eac167e3ed03673048fff89103fad52998d5023cdd0e" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 069dc791cc..1c77b83ded 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,42 +1,41 @@ -[tool.poetry] +[project] name = "app" version = "0.1.0" description = "" -authors = ["Admin "] - -[tool.poetry.dependencies] -python = "^3.10" -fastapi = {extras = ["standard"], version = "^0.114.2"} -python-multipart = "^0.0.7" -email-validator = "^2.1.0.post1" -passlib = {extras = ["bcrypt"], version = "^1.7.4"} -tenacity = "^8.2.3" -pydantic = ">2.0" -emails = "^0.6" - -gunicorn = "^22.0.0" -jinja2 = "^3.1.4" -alembic = "^1.12.1" -httpx = "^0.25.1" -psycopg = {extras = ["binary"], version = "^3.1.13"} -sqlmodel = "^0.0.21" -# Pin bcrypt until passlib supports the latest -bcrypt = "4.0.1" -pydantic-settings = "^2.2.1" -sentry-sdk = {extras = ["fastapi"], version = "^1.40.6"} -pyjwt = "^2.8.0" +requires-python = ">=3.10,<4.0" +dependencies = [ + "fastapi[standard]<1.0.0,>=0.114.2", + "python-multipart<1.0.0,>=0.0.7", + "email-validator<3.0.0.0,>=2.1.0.post1", + "passlib[bcrypt]<2.0.0,>=1.7.4", + "tenacity<9.0.0,>=8.2.3", + "pydantic>2.0", + "emails<1.0,>=0.6", + "jinja2<4.0.0,>=3.1.4", + "alembic<2.0.0,>=1.12.1", + "httpx<1.0.0,>=0.25.1", + "psycopg[binary]<4.0.0,>=3.1.13", + "sqlmodel<1.0.0,>=0.0.21", + # Pin bcrypt until passlib supports the latest + "bcrypt==4.0.1", + "pydantic-settings<3.0.0,>=2.2.1", + "sentry-sdk[fastapi]<2.0.0,>=1.40.6", + "pyjwt<3.0.0,>=2.8.0", +] -[tool.poetry.group.dev.dependencies] -pytest = "^7.4.3" -mypy = "^1.8.0" -ruff = "^0.2.2" -pre-commit = "^3.6.2" -types-passlib = "^1.7.7.20240106" -coverage = "^7.4.3" +[tool.uv] +dev-dependencies = [ + "pytest<8.0.0,>=7.4.3", + "mypy<2.0.0,>=1.8.0", + "ruff<1.0.0,>=0.2.2", + "pre-commit<4.0.0,>=3.6.2", + "types-passlib<2.0.0.0,>=1.7.7.20240106", + "coverage<8.0.0,>=7.4.3", +] [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" [tool.mypy] strict = true diff --git a/backend/uv.lock b/backend/uv.lock new file mode 100644 index 0000000000..cfc200d3c3 --- /dev/null +++ b/backend/uv.lock @@ -0,0 +1,1554 @@ +version = 1 +requires-python = ">=3.10, <4.0" +resolution-markers = [ + "python_full_version < '3.13'", + "python_full_version >= '3.13'", +] + +[[package]] +name = "alembic" +version = "1.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/e2/efa88e86029cada2da5941ec664d50d9a3b2a91f5066405c6f90e5016c16/alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef", size = 1206463 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/ed/c884465c33c25451e4a5cd4acad154c29e5341e3214e220e7f3478aa4b0d/alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953", size = 232990 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 }, +] + +[[package]] +name = "app" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "alembic" }, + { name = "bcrypt" }, + { name = "email-validator" }, + { name = "emails" }, + { name = "fastapi", extra = ["standard"] }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "passlib", extra = ["bcrypt"] }, + { name = "psycopg", extra = ["binary"] }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt" }, + { name = "python-multipart" }, + { name = "sentry-sdk", extra = ["fastapi"] }, + { name = "sqlmodel" }, + { name = "tenacity" }, +] + +[package.dev-dependencies] +dev = [ + { name = "coverage" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "types-passlib" }, +] + +[package.metadata] +requires-dist = [ + { name = "alembic", specifier = ">=1.12.1,<2.0.0" }, + { name = "bcrypt", specifier = "==4.0.1" }, + { name = "email-validator", specifier = ">=2.1.0.post1,<3.0.0.0" }, + { name = "emails", specifier = ">=0.6,<1.0" }, + { name = "fastapi", extras = ["standard"], specifier = ">=0.114.2,<1.0.0" }, + { name = "httpx", specifier = ">=0.25.1,<1.0.0" }, + { name = "jinja2", specifier = ">=3.1.4,<4.0.0" }, + { name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4,<2.0.0" }, + { name = "psycopg", extras = ["binary"], specifier = ">=3.1.13,<4.0.0" }, + { name = "pydantic", specifier = ">2.0" }, + { name = "pydantic-settings", specifier = ">=2.2.1,<3.0.0" }, + { name = "pyjwt", specifier = ">=2.8.0,<3.0.0" }, + { name = "python-multipart", specifier = ">=0.0.7,<1.0.0" }, + { name = "sentry-sdk", extras = ["fastapi"], specifier = ">=1.40.6,<2.0.0" }, + { name = "sqlmodel", specifier = ">=0.0.21,<1.0.0" }, + { name = "tenacity", specifier = ">=8.2.3,<9.0.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "coverage", specifier = ">=7.4.3,<8.0.0" }, + { name = "mypy", specifier = ">=1.8.0,<2.0.0" }, + { name = "pre-commit", specifier = ">=3.6.2,<4.0.0" }, + { name = "pytest", specifier = ">=7.4.3,<8.0.0" }, + { name = "ruff", specifier = ">=0.2.2,<1.0.0" }, + { name = "types-passlib", specifier = ">=1.7.7.20240106,<2.0.0.0" }, +] + +[[package]] +name = "bcrypt" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/ae/3af7d006aacf513975fd1948a6b4d6f8b4a307f8a244e1a3d3774b297aad/bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd", size = 25498 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/d4/3b2657bd58ef02b23a07729b0df26f21af97169dbd0b5797afa9e97ebb49/bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f", size = 473446 }, + { url = "https://files.pythonhosted.org/packages/ec/0a/1582790232fef6c2aa201f345577306b8bfe465c2c665dec04c86a016879/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0", size = 583044 }, + { url = "https://files.pythonhosted.org/packages/41/16/49ff5146fb815742ad58cafb5034907aa7f166b1344d0ddd7fd1c818bd17/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410", size = 583189 }, + { url = "https://files.pythonhosted.org/packages/aa/48/fd2b197a9741fa790ba0b88a9b10b5e88e62ff5cf3e1bc96d8354d7ce613/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344", size = 593473 }, + { url = "https://files.pythonhosted.org/packages/7d/50/e683d8418974a602ba40899c8a5c38b3decaf5a4d36c32fc65dce454d8a8/bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a", size = 593249 }, + { url = "https://files.pythonhosted.org/packages/fb/a7/ee4561fd9b78ca23c8e5591c150cc58626a5dfb169345ab18e1c2c664ee0/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3", size = 583586 }, + { url = "https://files.pythonhosted.org/packages/64/fe/da28a5916128d541da0993328dc5cf4b43dfbf6655f2c7a2abe26ca2dc88/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2", size = 593659 }, + { url = "https://files.pythonhosted.org/packages/dd/4f/3632a69ce344c1551f7c9803196b191a8181c6a1ad2362c225581ef0d383/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535", size = 613116 }, + { url = "https://files.pythonhosted.org/packages/87/69/edacb37481d360d06fc947dab5734aaf511acb7d1a1f9e2849454376c0f8/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e", size = 624290 }, + { url = "https://files.pythonhosted.org/packages/aa/ca/6a534669890725cbb8c1fb4622019be31813c8edaa7b6d5b62fc9360a17e/bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab", size = 159428 }, + { url = "https://files.pythonhosted.org/packages/46/81/d8c22cd7e5e1c6a7d48e41a1d1d46c92f17dae70a54d9814f746e6027dec/bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9", size = 152930 }, +] + +[[package]] +name = "cachetools" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/38/a0f315319737ecf45b4319a8cd1f3a908e29d9277b46942263292115eee7/cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a", size = 27661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/07/14f8ad37f2d12a5ce41206c21820d8cb6561b728e51fad4530dff0552a67/cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", size = 9524 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219 }, + { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521 }, + { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383 }, + { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223 }, + { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101 }, + { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699 }, + { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065 }, + { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505 }, + { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425 }, + { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287 }, + { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929 }, + { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605 }, + { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646 }, + { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846 }, + { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343 }, + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690 }, + { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127 }, + { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654 }, + { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598 }, + { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732 }, + { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816 }, + { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325 }, + { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418 }, + { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343 }, + { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136 }, + { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796 }, + { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244 }, + { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279 }, + { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859 }, + { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549 }, + { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477 }, + { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134 }, + { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910 }, + { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348 }, + { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230 }, + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983 }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221 }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342 }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371 }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455 }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924 }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252 }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897 }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606 }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373 }, + { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007 }, + { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269 }, + { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886 }, + { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037 }, + { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038 }, + { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690 }, + { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765 }, + { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611 }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671 }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368 }, + { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758 }, + { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035 }, + { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839 }, + { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569 }, + { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927 }, + { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401 }, + { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301 }, + { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598 }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307 }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453 }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926 }, +] + +[[package]] +name = "cssselect" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/91/d51202cc41fbfca7fa332f43a5adac4b253962588c7cc5a54824b019081c/cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc", size = 41423 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/a9/2da08717a6862c48f1d61ef957a7bba171e7eefa6c0aa0ceb96a140c2a6b/cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e", size = 18687 }, +] + +[[package]] +name = "cssutils" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/9f/329d26121fe165be44b1dfff21aa0dc348f04633931f1d20ed6cf448a236/cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2", size = 711657 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747 }, +] + +[[package]] +name = "distlib" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/91/e2df406fb4efacdf46871c25cde65d3c6ee5e173b7e5a4547a47bae91920/distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64", size = 609931 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", size = 468850 }, +] + +[[package]] +name = "dnspython" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/c871f55054e403fdfd6b8f65fd6d1c4e147ed100d3e9f9ba1fe695403939/dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc", size = 332727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/a1/8c5287991ddb8d3e4662f71356d9656d91ab3a36618c3dd11b280df0d255/dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50", size = 307696 }, +] + +[[package]] +name = "email-validator" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 }, +] + +[[package]] +name = "emails" +version = "0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, + { name = "cssutils" }, + { name = "lxml" }, + { name = "premailer" }, + { name = "python-dateutil" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/f9/c1e315aa82ed9f037186c30109200fb4b4c51b5483b8065daa0ca836a336/emails-0.6.tar.gz", hash = "sha256:a4c2d67ea8b8831967a750d8edc6e77040d7693143fe280e6d2a367d9c36ff88", size = 44066 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/7e/b648d640d88d31de49e566832aca9cce025c52d6349b0a0fc65e9df1f4c5/emails-0.6-py2.py3-none-any.whl", hash = "sha256:72c1e3198075709cc35f67e1b49e2da1a2bc087e9b444073db61a379adfb7f3c", size = 56250 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "fastapi" +version = "0.115.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/5e/bf0471f14bf6ebfbee8208148a3396d1a23298531a6cc10776c59f4c0f87/fastapi-0.115.0.tar.gz", hash = "sha256:f93b4ca3529a8ebc6fc3fcf710e5efa8de3df9b41570958abf1d97d843138004", size = 302295 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/ab/a1f7eed031aeb1c406a6e9d45ca04bff401c8a25a30dd0e4fd2caae767c3/fastapi-0.115.0-py3-none-any.whl", hash = "sha256:17ea427674467486e997206a5ab25760f6b09e069f099b96f5b55a32fb6f1631", size = 94625 }, +] + +[package.optional-dependencies] +standard = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["standard"] }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "python-multipart" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/f8/1ad5ce32d029aeb9117e9a5a9b3e314a8477525d60c12a9b7730a3c186ec/fastapi_cli-0.0.5.tar.gz", hash = "sha256:d30e1239c6f46fcb95e606f02cdda59a1e2fa778a54b64686b3ff27f6211ff9f", size = 15571 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/ea/4b5011012ac925fe2f83b19d0e09cee9d324141ec7bf5e78bb2817f96513/fastapi_cli-0.0.5-py3-none-any.whl", hash = "sha256:e94d847524648c748a5350673546bbf9bcaeb086b33c24f2e82e021436866a46", size = 9489 }, +] + +[package.optional-dependencies] +standard = [ + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235 }, + { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168 }, + { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826 }, + { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443 }, + { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295 }, + { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544 }, + { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456 }, + { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111 }, + { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392 }, + { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479 }, + { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404 }, + { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813 }, + { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517 }, + { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831 }, + { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413 }, + { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619 }, + { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198 }, + { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930 }, + { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, + { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, + { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, + { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, + { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, + { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, + { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, + { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, + { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/b0/5e8b8674f8d203335a62fdfcfa0d11ebe09e23613c3391033cbba35f7926/httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61", size = 83234 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/d4/e5d7e4f2174f8a4d63c8897d79eb8fe2503f7ecc03282fee1fa2719c2704/httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5", size = 77926 }, +] + +[[package]] +name = "httptools" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/d77686502fced061b3ead1c35a2d70f6b281b5f723c4eff7a2277c04e4a2/httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", size = 191228 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6a/80bce0216b63babf51cdc34814c3f0f10489e13ab89fb6bc91202736a8a2/httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f", size = 149778 }, + { url = "https://files.pythonhosted.org/packages/bd/7d/4cd75356dfe0ed0b40ca6873646bf9ff7b5138236c72338dc569dc57d509/httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563", size = 77604 }, + { url = "https://files.pythonhosted.org/packages/4e/74/6348ce41fb5c1484f35184c172efb8854a288e6090bb54e2210598268369/httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58", size = 346717 }, + { url = "https://files.pythonhosted.org/packages/65/e7/dd5ba95c84047118a363f0755ad78e639e0529be92424bb020496578aa3b/httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185", size = 341442 }, + { url = "https://files.pythonhosted.org/packages/d8/97/b37d596bc32be291477a8912bf9d1508d7e8553aa11a30cd871fd89cbae4/httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142", size = 354531 }, + { url = "https://files.pythonhosted.org/packages/99/c9/53ed7176583ec4b4364d941a08624288f2ae55b4ff58b392cdb68db1e1ed/httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658", size = 347754 }, + { url = "https://files.pythonhosted.org/packages/1e/fc/8a26c2adcd3f141e4729897633f03832b71ebea6f4c31cce67a92ded1961/httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b", size = 58165 }, + { url = "https://files.pythonhosted.org/packages/f5/d1/53283b96ed823d5e4d89ee9aa0f29df5a1bdf67f148e061549a595d534e4/httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1", size = 145855 }, + { url = "https://files.pythonhosted.org/packages/80/dd/cebc9d4b1d4b70e9f3d40d1db0829a28d57ca139d0b04197713816a11996/httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0", size = 75604 }, + { url = "https://files.pythonhosted.org/packages/76/7a/45c5a9a2e9d21f7381866eb7b6ead5a84d8fe7e54e35208eeb18320a29b4/httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc", size = 324784 }, + { url = "https://files.pythonhosted.org/packages/59/23/047a89e66045232fb82c50ae57699e40f70e073ae5ccd53f54e532fbd2a2/httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2", size = 318547 }, + { url = "https://files.pythonhosted.org/packages/82/f5/50708abc7965d7d93c0ee14a148ccc6d078a508f47fe9357c79d5360f252/httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837", size = 330211 }, + { url = "https://files.pythonhosted.org/packages/e3/1e/9823ca7aab323c0e0e9dd82ce835a6e93b69f69aedffbc94d31e327f4283/httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d", size = 322174 }, + { url = "https://files.pythonhosted.org/packages/14/e4/20d28dfe7f5b5603b6b04c33bb88662ad749de51f0c539a561f235f42666/httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3", size = 55434 }, + { url = "https://files.pythonhosted.org/packages/60/13/b62e086b650752adf9094b7e62dab97f4cb7701005664544494b7956a51e/httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0", size = 146354 }, + { url = "https://files.pythonhosted.org/packages/f8/5d/9ad32b79b6c24524087e78aa3f0a2dfcf58c11c90e090e4593b35def8a86/httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2", size = 75785 }, + { url = "https://files.pythonhosted.org/packages/d0/a4/b503851c40f20bcbd453db24ed35d961f62abdae0dccc8f672cd5d350d87/httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90", size = 345396 }, + { url = "https://files.pythonhosted.org/packages/a2/9a/aa406864f3108e06f7320425a528ff8267124dead1fd72a3e9da2067f893/httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503", size = 344741 }, + { url = "https://files.pythonhosted.org/packages/cf/3a/3fd8dfb987c4247651baf2ac6f28e8e9f889d484ca1a41a9ad0f04dfe300/httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84", size = 345096 }, + { url = "https://files.pythonhosted.org/packages/80/01/379f6466d8e2edb861c1f44ccac255ed1f8a0d4c5c666a1ceb34caad7555/httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb", size = 343535 }, + { url = "https://files.pythonhosted.org/packages/d3/97/60860e9ee87a7d4712b98f7e1411730520053b9d69e9e42b0b9751809c17/httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949", size = 55660 }, +] + +[[package]] +name = "httpx" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, +] + +[[package]] +name = "identify" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bb/25024dbcc93516c492b75919e76f389bac754a3e4248682fba32b250c880/identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98", size = 99097 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/0c/4ef72754c050979fdcc06c744715ae70ea37e734816bb6514f79df77a42f/identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0", size = 98972 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "lxml" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ce/2789e39eddf2b13fac29878bfa465f0910eb6b0096e29090e5176bc8cf43/lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656", size = 8124570 }, + { url = "https://files.pythonhosted.org/packages/24/a8/f4010166a25d41715527129af2675981a50d3bbf7df09c5d9ab8ca24fbf9/lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d", size = 4413042 }, + { url = "https://files.pythonhosted.org/packages/41/a4/7e45756cecdd7577ddf67a68b69c1db0f5ddbf0c9f65021ee769165ffc5a/lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a", size = 5139213 }, + { url = "https://files.pythonhosted.org/packages/02/e2/ecf845b12323c92748077e1818b64e8b4dba509a4cb12920b3762ebe7552/lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8", size = 4838814 }, + { url = "https://files.pythonhosted.org/packages/12/91/619f9fb72cf75e9ceb8700706f7276f23995f6ad757e6d400fbe35ca4990/lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330", size = 5425084 }, + { url = "https://files.pythonhosted.org/packages/25/3b/162a85a8f0fd2a3032ec3f936636911c6e9523a8e263fffcfd581ce98b54/lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965", size = 4875993 }, + { url = "https://files.pythonhosted.org/packages/43/af/dd3f58cc7d946da6ae42909629a2b1d5dd2d1b583334d4af9396697d6863/lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22", size = 5012462 }, + { url = "https://files.pythonhosted.org/packages/69/c1/5ea46b2d4c98f5bf5c83fffab8a0ad293c9bc74df9ecfbafef10f77f7201/lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b", size = 4815288 }, + { url = "https://files.pythonhosted.org/packages/1d/51/a0acca077ad35da458f4d3f729ef98effd2b90f003440d35fc36323f8ae6/lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7", size = 5472435 }, + { url = "https://files.pythonhosted.org/packages/4d/6b/0989c9368986961a6b0f55b46c80404c4b758417acdb6d87bfc3bd5f4967/lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8", size = 4976354 }, + { url = "https://files.pythonhosted.org/packages/05/9e/87492d03ff604fbf656ed2bf3e2e8d28f5d58ea1f00ff27ac27b06509079/lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32", size = 5029973 }, + { url = "https://files.pythonhosted.org/packages/f9/cc/9ae1baf5472af88e19e2c454b3710c1be9ecafb20eb474eeabcd88a055d2/lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86", size = 4888837 }, + { url = "https://files.pythonhosted.org/packages/d2/10/5594ffaec8c120d75b17e3ad23439b740a51549a9b5fd7484b2179adfe8f/lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5", size = 5530555 }, + { url = "https://files.pythonhosted.org/packages/ea/9b/de17f05377c8833343b629905571fb06cff2028f15a6f58ae2267662e341/lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03", size = 5405314 }, + { url = "https://files.pythonhosted.org/packages/8a/b4/227be0f1f3cca8255925985164c3838b8b36e441ff0cc10c1d3c6bdba031/lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7", size = 5079303 }, + { url = "https://files.pythonhosted.org/packages/5c/ee/19abcebb7fc40319bb71cd6adefa1ad94d09b5660228715854d6cc420713/lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80", size = 3475126 }, + { url = "https://files.pythonhosted.org/packages/a1/35/183d32551447e280032b2331738cd850da435a42f850b71ebeaab42c1313/lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3", size = 3805065 }, + { url = "https://files.pythonhosted.org/packages/5c/a8/449faa2a3cbe6a99f8d38dcd51a3ee8844c17862841a6f769ea7c2a9cd0f/lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", size = 8141056 }, + { url = "https://files.pythonhosted.org/packages/ac/8a/ae6325e994e2052de92f894363b038351c50ee38749d30cc6b6d96aaf90f/lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", size = 4425238 }, + { url = "https://files.pythonhosted.org/packages/f8/fb/128dddb7f9086236bce0eeae2bfb316d138b49b159f50bc681d56c1bdd19/lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", size = 5095197 }, + { url = "https://files.pythonhosted.org/packages/b4/f9/a181a8ef106e41e3086629c8bdb2d21a942f14c84a0e77452c22d6b22091/lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", size = 4809809 }, + { url = "https://files.pythonhosted.org/packages/25/2f/b20565e808f7f6868aacea48ddcdd7e9e9fb4c799287f21f1a6c7c2e8b71/lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", size = 5407593 }, + { url = "https://files.pythonhosted.org/packages/23/0e/caac672ec246d3189a16c4d364ed4f7d6bf856c080215382c06764058c08/lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", size = 4866657 }, + { url = "https://files.pythonhosted.org/packages/67/a4/1f5fbd3f58d4069000522196b0b776a014f3feec1796da03e495cf23532d/lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", size = 4967017 }, + { url = "https://files.pythonhosted.org/packages/ee/73/623ecea6ca3c530dd0a4ed0d00d9702e0e85cd5624e2d5b93b005fe00abd/lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", size = 4810730 }, + { url = "https://files.pythonhosted.org/packages/1d/ce/fb84fb8e3c298f3a245ae3ea6221c2426f1bbaa82d10a88787412a498145/lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", size = 5455154 }, + { url = "https://files.pythonhosted.org/packages/b1/72/4d1ad363748a72c7c0411c28be2b0dc7150d91e823eadad3b91a4514cbea/lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", size = 4969416 }, + { url = "https://files.pythonhosted.org/packages/42/07/b29571a58a3a80681722ea8ed0ba569211d9bb8531ad49b5cacf6d409185/lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", size = 5013672 }, + { url = "https://files.pythonhosted.org/packages/b9/93/bde740d5a58cf04cbd38e3dd93ad1e36c2f95553bbf7d57807bc6815d926/lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", size = 4878644 }, + { url = "https://files.pythonhosted.org/packages/56/b5/645c8c02721d49927c93181de4017164ec0e141413577687c3df8ff0800f/lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", size = 5511531 }, + { url = "https://files.pythonhosted.org/packages/85/3f/6a99a12d9438316f4fc86ef88c5d4c8fb674247b17f3173ecadd8346b671/lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", size = 5402065 }, + { url = "https://files.pythonhosted.org/packages/80/8a/df47bff6ad5ac57335bf552babfb2408f9eb680c074ec1ba412a1a6af2c5/lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", size = 5069775 }, + { url = "https://files.pythonhosted.org/packages/08/ae/e7ad0f0fbe4b6368c5ee1e3ef0c3365098d806d42379c46c1ba2802a52f7/lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", size = 3474226 }, + { url = "https://files.pythonhosted.org/packages/c3/b5/91c2249bfac02ee514ab135e9304b89d55967be7e53e94a879b74eec7a5c/lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", size = 3814971 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", size = 8171753 }, + { url = "https://files.pythonhosted.org/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", size = 4441955 }, + { url = "https://files.pythonhosted.org/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", size = 5050778 }, + { url = "https://files.pythonhosted.org/packages/88/69/6972bfafa8cd3ddc8562b126dd607011e218e17be313a8b1b9cc5a0ee876/lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e", size = 4748628 }, + { url = "https://files.pythonhosted.org/packages/5d/ea/a6523c7c7f6dc755a6eed3d2f6d6646617cad4d3d6d8ce4ed71bfd2362c8/lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179", size = 5322215 }, + { url = "https://files.pythonhosted.org/packages/99/37/396fbd24a70f62b31d988e4500f2068c7f3fd399d2fd45257d13eab51a6f/lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a", size = 4813963 }, + { url = "https://files.pythonhosted.org/packages/09/91/e6136f17459a11ce1757df864b213efbeab7adcb2efa63efb1b846ab6723/lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3", size = 4923353 }, + { url = "https://files.pythonhosted.org/packages/1d/7c/2eeecf87c9a1fca4f84f991067c693e67340f2b7127fc3eca8fa29d75ee3/lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1", size = 4740541 }, + { url = "https://files.pythonhosted.org/packages/3b/ed/4c38ba58defca84f5f0d0ac2480fdcd99fc7ae4b28fc417c93640a6949ae/lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d", size = 5346504 }, + { url = "https://files.pythonhosted.org/packages/a5/22/bbd3995437e5745cb4c2b5d89088d70ab19d4feabf8a27a24cecb9745464/lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c", size = 4898077 }, + { url = "https://files.pythonhosted.org/packages/0a/6e/94537acfb5b8f18235d13186d247bca478fea5e87d224644e0fe907df976/lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99", size = 4946543 }, + { url = "https://files.pythonhosted.org/packages/8d/e8/4b15df533fe8e8d53363b23a41df9be907330e1fa28c7ca36893fad338ee/lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff", size = 4816841 }, + { url = "https://files.pythonhosted.org/packages/1a/e7/03f390ea37d1acda50bc538feb5b2bda6745b25731e4e76ab48fae7106bf/lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a", size = 5417341 }, + { url = "https://files.pythonhosted.org/packages/ea/99/d1133ab4c250da85a883c3b60249d3d3e7c64f24faff494cf0fd23f91e80/lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8", size = 5327539 }, + { url = "https://files.pythonhosted.org/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", size = 5012542 }, + { url = "https://files.pythonhosted.org/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", size = 3486454 }, + { url = "https://files.pythonhosted.org/packages/fc/82/ace5a5676051e60355bd8fb945df7b1ba4f4fb8447f2010fb816bfd57724/lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", size = 3816857 }, + { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 }, + { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 }, + { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 }, + { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 }, + { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 }, + { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 }, + { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 }, + { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 }, + { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 }, + { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 }, + { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 }, + { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 }, + { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 }, + { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 }, + { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 }, + { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, + { url = "https://files.pythonhosted.org/packages/99/f7/b73a431c8500565aa500e99e60b448d305eaf7c0b4c893c7c5a8a69cc595/lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c", size = 3925431 }, + { url = "https://files.pythonhosted.org/packages/db/48/4a206623c0d093d0e3b15f415ffb4345b0bdf661a3d0b15a112948c033c7/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a", size = 4216683 }, + { url = "https://files.pythonhosted.org/packages/54/47/577820c45dd954523ae8453b632d91e76da94ca6d9ee40d8c98dd86f916b/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005", size = 4326732 }, + { url = "https://files.pythonhosted.org/packages/68/de/96cb6d3269bc994b4f5ede8ca7bf0840f5de0a278bc6e50cb317ff71cafa/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce", size = 4218377 }, + { url = "https://files.pythonhosted.org/packages/a5/43/19b1ef6cbffa4244a217f95cc5f41a6cb4720fed33510a49670b03c5f1a0/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83", size = 4351237 }, + { url = "https://files.pythonhosted.org/packages/ba/b2/6a22fb5c0885da3b00e116aee81f0b829ec9ac8f736cd414b4a09413fc7d/lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba", size = 3487557 }, +] + +[[package]] +name = "mako" +version = "1.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/03/fb5ba97ff65ce64f6d35b582aacffc26b693a98053fa831ab43a437cbddb/Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc", size = 392738 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/62/70f5a0c2dd208f9f3f2f9afd103aec42ee4d9ad2401d78342f75e9b8da36/Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a", size = 78565 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206 }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079 }, + { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620 }, + { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818 }, + { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493 }, + { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630 }, + { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745 }, + { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021 }, + { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659 }, + { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213 }, + { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219 }, + { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098 }, + { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014 }, + { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220 }, + { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756 }, + { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988 }, + { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718 }, + { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317 }, + { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670 }, + { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224 }, + { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 }, + { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 }, + { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 }, + { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 }, + { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 }, + { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 }, + { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 }, + { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 }, + { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 }, + { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "more-itertools" +version = "10.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", size = 121020 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", size = 60952 }, +] + +[[package]] +name = "mypy" +version = "1.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/86/5d7cbc4974fd564550b80fbb8103c05501ea11aa7835edf3351d90095896/mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", size = 3078806 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/cd/815368cd83c3a31873e5e55b317551500b12f2d1d7549720632f32630333/mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", size = 10939401 }, + { url = "https://files.pythonhosted.org/packages/f1/27/e18c93a195d2fad75eb96e1f1cbc431842c332e8eba2e2b77eaf7313c6b7/mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", size = 10111697 }, + { url = "https://files.pythonhosted.org/packages/dc/08/cdc1fc6d0d5a67d354741344cc4aa7d53f7128902ebcbe699ddd4f15a61c/mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", size = 12500508 }, + { url = "https://files.pythonhosted.org/packages/64/12/aad3af008c92c2d5d0720ea3b6674ba94a98cdb86888d389acdb5f218c30/mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", size = 13020712 }, + { url = "https://files.pythonhosted.org/packages/03/e6/a7d97cc124a565be5e9b7d5c2a6ebf082379ffba99646e4863ed5bbcb3c3/mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", size = 9567319 }, + { url = "https://files.pythonhosted.org/packages/e2/aa/cc56fb53ebe14c64f1fe91d32d838d6f4db948b9494e200d2f61b820b85d/mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", size = 10859630 }, + { url = "https://files.pythonhosted.org/packages/04/c8/b19a760fab491c22c51975cf74e3d253b8c8ce2be7afaa2490fbf95a8c59/mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", size = 10037973 }, + { url = "https://files.pythonhosted.org/packages/88/57/7e7e39f2619c8f74a22efb9a4c4eff32b09d3798335625a124436d121d89/mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", size = 12416659 }, + { url = "https://files.pythonhosted.org/packages/fc/a6/37f7544666b63a27e46c48f49caeee388bf3ce95f9c570eb5cfba5234405/mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", size = 12897010 }, + { url = "https://files.pythonhosted.org/packages/84/8b/459a513badc4d34acb31c736a0101c22d2bd0697b969796ad93294165cfb/mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", size = 9562873 }, + { url = "https://files.pythonhosted.org/packages/35/3a/ed7b12ecc3f6db2f664ccf85cb2e004d3e90bec928e9d7be6aa2f16b7cdf/mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", size = 10990335 }, + { url = "https://files.pythonhosted.org/packages/04/e4/1a9051e2ef10296d206519f1df13d2cc896aea39e8683302f89bf5792a59/mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", size = 10007119 }, + { url = "https://files.pythonhosted.org/packages/f3/3c/350a9da895f8a7e87ade0028b962be0252d152e0c2fbaafa6f0658b4d0d4/mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", size = 12506856 }, + { url = "https://files.pythonhosted.org/packages/b6/49/ee5adf6a49ff13f4202d949544d3d08abb0ea1f3e7f2a6d5b4c10ba0360a/mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", size = 12952066 }, + { url = "https://files.pythonhosted.org/packages/27/c0/b19d709a42b24004d720db37446a42abadf844d5c46a2c442e2a074d70d9/mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", size = 9664000 }, + { url = "https://files.pythonhosted.org/packages/42/3a/bdf730640ac523229dd6578e8a581795720a9321399de494374afc437ec5/mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", size = 2619625 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + +[[package]] +name = "passlib" +version = "1.7.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554 }, +] + +[package.optional-dependencies] +bcrypt = [ + { name = "bcrypt" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pre-commit" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/10/97ee2fa54dff1e9da9badbc5e35d0bbaef0776271ea5907eccf64140f72f/pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", size = 177815 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/92/caae8c86e94681b42c246f0bca35c059a2f0529e5b92619f6aba4cf7e7b6/pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f", size = 204643 }, +] + +[[package]] +name = "premailer" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "cssselect" }, + { name = "cssutils" }, + { name = "lxml" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/e49bd31941eff2987076383fa6d811eb785a28f498f5bb131e981bd71e13/premailer-3.10.0.tar.gz", hash = "sha256:d1875a8411f5dc92b53ef9f193db6c0f879dc378d618e0ad292723e388bfe4c2", size = 24342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/07/4e8d94f94c7d41ca5ddf8a9695ad87b888104e2fd41a35546c1dc9ca74ac/premailer-3.10.0-py2.py3-none-any.whl", hash = "sha256:021b8196364d7df96d04f9ade51b794d0b77bcc19e998321c515633a2273be1a", size = 19544 }, +] + +[[package]] +name = "psycopg" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/70/d1e4c251be6e0752cbc7408f0556f8f922690837309442b9019122295712/psycopg-3.2.2.tar.gz", hash = "sha256:8bad2e497ce22d556dac1464738cb948f8d6bab450d965cf1d8a8effd52412e0", size = 155483 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/89/e63ec25b80290c4a923cdb5ecd5dbc85e310f93fb84b7f294006c9269d95/psycopg-3.2.2-py3-none-any.whl", hash = "sha256:babf565d459d8f72fb65da5e211dd0b58a52c51e4e1fa9cadecff42d6b7619b2", size = 197852 }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/42/f5a181d07c0ae5c8091449fda45d562d3b0861c127b94d7009eaea45c61f/psycopg_binary-3.2.2-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:8eacbf58d4f8d7bc82e0a60476afa2622b5a58f639a3cc2710e3e37b72aff3cb", size = 3381668 }, + { url = "https://files.pythonhosted.org/packages/ce/fb/66d2e3e5d550ba3b9d33e30bf6d5beb871a85eb95553c851fce7f09f8a1e/psycopg_binary-3.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:d07e62476ee8c54853b2b8cfdf3858a574218103b4cd213211f64326c7812437", size = 3502272 }, + { url = "https://files.pythonhosted.org/packages/f0/8d/758da39eca57f046ee712ad4c310840bcc08d889042d1b297cd28c78e909/psycopg_binary-3.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c22e615ee0ecfc6687bb8a39a4ed9d6bac030b5e72ac15e7324fd6e48979af71", size = 4467251 }, + { url = "https://files.pythonhosted.org/packages/91/bb/1abb1ccc318eb878acf9637479334de7406529516126e4af48b16dd85426/psycopg_binary-3.2.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec29c7ec136263628e3f09a53e51d0a4b1ad765a6e45135707bfa848b39113f9", size = 4268614 }, + { url = "https://files.pythonhosted.org/packages/f5/1a/14b4ae68f1c7cfba543883987d2f134eca31b0983bb684a52e0f51f3ac21/psycopg_binary-3.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:035753f80cbbf6aceca6386f53e139df70c7aca057b0592711047b5a8cfef8bb", size = 4512352 }, + { url = "https://files.pythonhosted.org/packages/12/44/53df01c7c7cffb351cafa88c58692fab0ab962edd89f22974cbfc38b6677/psycopg_binary-3.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ee99336151ff7c30682f2ef9cb1174d235bc1471322faabba97f9db1398167", size = 4212477 }, + { url = "https://files.pythonhosted.org/packages/b7/31/c918927692fc5a9c4db0a7c454e1595e9d40378d5c526d26505f310e4068/psycopg_binary-3.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a60674dff4a4194e88312b463fb84ac80924c2b9e25d0e0460f3176bf1af4a6b", size = 3137907 }, + { url = "https://files.pythonhosted.org/packages/cb/65/538aa057b3e8245a31ea8baac93df9947ee1b2ebf4c02014a556cddd875e/psycopg_binary-3.2.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3c701507a49340de422d77a6ce95918a0019990bbf27daec35aa40050c6eadb6", size = 3113363 }, + { url = "https://files.pythonhosted.org/packages/dc/81/eaee4f05bcba19984615e90319c429d125d07e5f0fe8c8ec3025901de4df/psycopg_binary-3.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1b3c5a04eaf8866e399315cff2e810260cce10b797437a9f49fd71b5f4b94d0a", size = 3220512 }, + { url = "https://files.pythonhosted.org/packages/48/cc/1d0f82a47216f925e36be6f6d7be61984a5168ff8c0496c57f468cc0e219/psycopg_binary-3.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ad9c09de4c262f516ae6891d042a4325649b18efa39dd82bbe0f7bc95c37bfb", size = 3255023 }, + { url = "https://files.pythonhosted.org/packages/d0/29/c45760ba6218eae37474aa5f46c1f55b290a6d4b86c0c59e60fa5613257a/psycopg_binary-3.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:bf1d3582185cb43ecc27403bee2f5405b7a45ccaab46c8508d9a9327341574fc", size = 2921688 }, + { url = "https://files.pythonhosted.org/packages/1f/1a/76299ad86a01f57a67961c4a45ce06c6eb8e76b8bc7bfb92548c62a6fa72/psycopg_binary-3.2.2-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:554d208757129d34fa47b7c890f9ef922f754e99c6b089cb3a209aa0fe282682", size = 3390336 }, + { url = "https://files.pythonhosted.org/packages/c2/1d/04fbcadd568eb0ee04b0d99286fe4ffd6c76c9cdd130e58d477617b77941/psycopg_binary-3.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:71dc3cc10d1fd7d26a3079d0a5b4a8e8ad0d7b89a702ceb7605a52e4395be122", size = 3507406 }, + { url = "https://files.pythonhosted.org/packages/60/00/094a437f68d83fef4dd139630dfb0e060fcf2a7ac68fffdb63b2f3eaa43a/psycopg_binary-3.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86f578d63f2e1fdf87c9adaed4ff23d7919bda8791cf1380fa4cf3a857ccb8b", size = 4463745 }, + { url = "https://files.pythonhosted.org/packages/ea/de/0303e807a33251dec41aec709c3041b9ffd86b67d997088c504a24e90ba3/psycopg_binary-3.2.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a4eb737682c02a602a12aa85a492608066f77793dab681b1c4e885fedc160b1", size = 4263212 }, + { url = "https://files.pythonhosted.org/packages/3f/0d/8fa059bd936bb8e95164cc549d2eaaeaeb7df3a069bbb0ea01b48fab10a4/psycopg_binary-3.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e120a576e74e4e612c48f4b021e322e320ca102534d78a0ca4db2ffd058ae8d", size = 4513242 }, + { url = "https://files.pythonhosted.org/packages/1f/a5/9904c4ae040eef6cdb81c04e43b834302cfd3e47ee7cab8878d114abb168/psycopg_binary-3.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849d518e7d4c6186e1e48ea2ac2671912edf7e732fffe6f01dfed61cf0245de4", size = 4207852 }, + { url = "https://files.pythonhosted.org/packages/07/b7/24438b2ecb3ae8ceea44cf6e2bb92baac6be9b3d92c2940c89b3aa8e520e/psycopg_binary-3.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ee2b19152bcec8f356f989c31768702be5f139b4d51094273c4a9ddc8c55380", size = 3134053 }, + { url = "https://files.pythonhosted.org/packages/83/e3/d0157858ad814cdc6cf9f9b7543c736f6b56ab9d8dc1b4ca56908ec03586/psycopg_binary-3.2.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:00273dd011892e8216fcef76b42f775ddaa6348664a7fffae2a27c9557f45bfa", size = 3110817 }, + { url = "https://files.pythonhosted.org/packages/9f/fc/8554c822a80a08cd17b9e2a4e8fc098c940e972e01bc9e3f3774b9e02d54/psycopg_binary-3.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4bcb489615d7e56d1de42937e6a0fc13f766505729afdb54c2947a52db295220", size = 3214760 }, + { url = "https://files.pythonhosted.org/packages/6a/4d/a12d8a301fbd4416ebdb3f019c777a17edea0452278f630f83237cbcc3d4/psycopg_binary-3.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:06963f88916a177df95aaed27101af0989ba206654743b1a0e050b9d8e734686", size = 3253951 }, + { url = "https://files.pythonhosted.org/packages/09/0f/120b190ddaf6afed1eaa2fbc89e29ec810d8af44ff3599521f69f89b64b3/psycopg_binary-3.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed1ad836a0c21890c7f84e73c7ef1ed0950e0e4b0d8e49b609b6fd9c13f2ca21", size = 2924949 }, + { url = "https://files.pythonhosted.org/packages/1e/9a/68b76a795fe620c8848c758d12860b8b94998f374882dbf8ea4bc343b9e1/psycopg_binary-3.2.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:0dd314229885a81f9497875295d8788e651b78945627540f1e78ed71595e614a", size = 3361334 }, + { url = "https://files.pythonhosted.org/packages/0d/0c/f91242672c58bce7c290e11128569fe66ed27552388499cd80d75a5d4d0d/psycopg_binary-3.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:989acbe2f552769cdb780346cea32d86e7c117044238d5172ac10b025fe47194", size = 3504380 }, + { url = "https://files.pythonhosted.org/packages/e4/45/5fa47240357dea3646f3492d20141a5869cfaedcd5c64499622db7b17a8f/psycopg_binary-3.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566b1c530898590f0ac9d949cf94351c08d73c89f8800c74c0a63ffd89a383c8", size = 4443783 }, + { url = "https://files.pythonhosted.org/packages/ee/e5/9da098d1f7c1b064b39a2499cb4dfebe8fa5a48a132c3f544dab994199c4/psycopg_binary-3.2.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68d03efab7e2830a0df3aa4c29a708930e3f6b9fd98774ff9c4fd1f33deafecc", size = 4247070 }, + { url = "https://files.pythonhosted.org/packages/ba/44/c905a0ce2c66c0250a4ddce8eef41edc728bd2055ecaf8bd23468110c3f4/psycopg_binary-3.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e1f013bfb744023df23750fde51edcb606def8328473361db3c192c392c6060", size = 4483735 }, + { url = "https://files.pythonhosted.org/packages/30/2d/9f6bfcff78b643d220e088d91103fde70d193b9745d8999c7654ad45cd65/psycopg_binary-3.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a06136aab55a2de7dd4e2555badae276846827cfb023e6ba1b22f7a7b88e3f1b", size = 4186284 }, + { url = "https://files.pythonhosted.org/packages/44/48/79e7886a28818fdb4d5d39a86b5769bb33681ac23efe23accdaab42514c6/psycopg_binary-3.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:020c5154be144a1440cf87eae012b9004fb414ae4b9e7b1b9fb808fe39e96e83", size = 3110593 }, + { url = "https://files.pythonhosted.org/packages/5c/93/83d5610d259feb1d4d2d37cc0e1781f0d1632c885f5e2f85808b5b196552/psycopg_binary-3.2.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef341c556aeaa43a2729b07b04e20bfffdcf3d96c4a96e728ca94fe4ce632d8c", size = 3095074 }, + { url = "https://files.pythonhosted.org/packages/b6/94/3126db7a06fa9fe2ab3b1d6dd7a4add6bc1596b6864e01a77239702827b4/psycopg_binary-3.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66de2dd7d37bf66eb234ca9d907f5cd8caca43ff8d8a50dd5c15844d1cf0390c", size = 3184181 }, + { url = "https://files.pythonhosted.org/packages/6c/0e/6cce5ffaa25a25ede5ff08e757232bb425cacafe622627f29d286774073b/psycopg_binary-3.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2eb6f8f410dbbb71b8c633f283b8588b63bee0a7321f00ab76e9c800c593f732", size = 3229942 }, + { url = "https://files.pythonhosted.org/packages/10/31/951247b07205711115307f36ec3dbf6726101e086562febf6f989cbd6b95/psycopg_binary-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b45553c6b614d02e1486585980afdfd18f0000aac668e2e87c6e32da1adb051a", size = 2912528 }, + { url = "https://files.pythonhosted.org/packages/87/e5/245f749abdfc33b42ec2bc4d89fe2cdb29cd40dca7156d0e09308c33f933/psycopg_binary-3.2.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:1ee891287c2da57e7fee31fbe2fbcdf57125768133d811b02e9523d5a052eb28", size = 3358682 }, + { url = "https://files.pythonhosted.org/packages/93/dc/047a90e2bfd80a8414f5a203c7ff1747e3b3f43231c3c8059e8be91849cc/psycopg_binary-3.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5e95e4a8076ac7611e571623e1113fa84fd48c0459601969ffbf534d7aa236e7", size = 3500354 }, + { url = "https://files.pythonhosted.org/packages/df/72/b905dec41c30a8aad21f7767b21d3e5d3b9a7e92c1844678e4083d79257b/psycopg_binary-3.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6269d79a3d7d76b6fcf0fafae8444da00e83777a6c68c43851351a571ad37155", size = 4445322 }, + { url = "https://files.pythonhosted.org/packages/aa/41/aef11d4cda1af4a8181fbd578af39d6920232624fc6222f6b2f9758cc0e0/psycopg_binary-3.2.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6dd5d21a298c3c53af20ced8da4ae4cd038c6fe88c80842a8888fa3660b2094", size = 4248626 }, + { url = "https://files.pythonhosted.org/packages/6c/75/39ed8598f44188e4985f31f2639aa9894851fdfbf061bf926744b08b5790/psycopg_binary-3.2.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cf64e41e238620f05aad862f06bc8424f8f320d8075f1499bd85a225d18bd57", size = 4485767 }, + { url = "https://files.pythonhosted.org/packages/00/5a/ecdc4cf957d0658f77cc6fa61f6ee2e5118c914e5f93497375023389a1e5/psycopg_binary-3.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c482c3236ded54add31136a91d5223b233ec301f297fa2db79747404222dca6", size = 4188840 }, + { url = "https://files.pythonhosted.org/packages/2d/71/af4c47a665d13d2477085f77fb64195da5d6463dd54fc3a8bdfd5c082d24/psycopg_binary-3.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0718be095cefdad712542169d16fa58b3bd9200a3de1b0217ae761cdec1cf569", size = 3114998 }, + { url = "https://files.pythonhosted.org/packages/38/8f/6d56168d2ce7e7d802e09a4288faceb52f28bd4023cde72ede9e848c9f9b/psycopg_binary-3.2.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fb303b03c243a9041e1873b596e246f7caaf01710b312fafa65b1db5cd77dd6f", size = 3095882 }, + { url = "https://files.pythonhosted.org/packages/8b/76/c77643d97292673d8a5e3eea643812d585993155658f840c86bfa855e077/psycopg_binary-3.2.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:705da5bc4364bd7529473225fca02b795653bc5bd824dbe43e1df0b1a40fe691", size = 3189435 }, + { url = "https://files.pythonhosted.org/packages/30/31/b4ea793bdf44acca51e3fa6f68cc80d03725e8ef87fc2ee2b332c49fa521/psycopg_binary-3.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:05406b96139912574571b1c56bb023839a9146cf4b57c4548f36251dd5909fa1", size = 3233951 }, + { url = "https://files.pythonhosted.org/packages/49/e3/633d6d05e40651acb30458e296c90e878fa4caf3b3c21bb9e6adc912b811/psycopg_binary-3.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:7c357cf87e8d7612cfe781225be7669f35038a765d1b53ec9605f6c5aef9ee85", size = 2913412 }, +] + +[[package]] +name = "pydantic" +version = "2.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, +] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 }, + { url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 }, + { url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 }, + { url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 }, + { url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 }, + { url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 }, + { url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 }, + { url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 }, + { url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 }, + { url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 }, + { url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 }, + { url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, + { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, + { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, + { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, + { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, + { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, + { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, + { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, + { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, + { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, + { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, + { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, + { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, + { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, + { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, + { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, + { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, + { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, + { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, + { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, + { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, + { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, + { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, + { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, + { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, + { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, + { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, + { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, + { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, + { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, + { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, + { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, + { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, + { url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 }, + { url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 }, + { url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 }, + { url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 }, + { url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 }, + { url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 }, + { url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 }, + { url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 }, +] + +[[package]] +name = "pydantic-settings" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/27/0bed9dd26b93328b60a1402febc780e7be72b42847fa8b5c94b7d0aeb6d1/pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0", size = 70938 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/8d/29e82e333f32d9e2051c10764b906c2a6cd140992910b5f49762790911ba/pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907", size = 26864 }, +] + +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "pyjwt" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 }, +] + +[[package]] +name = "pytest" +version = "7.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "python-multipart" +version = "0.0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/29/0e5c896ec896b4e501bafa80ab555bbf3bcb0d720e9e33f908179aeb1a61/python_multipart-0.0.10.tar.gz", hash = "sha256:46eb3c6ce6fdda5fb1a03c7e11d490e407c6930a2703fe7aef4da71c374688fa", size = 34619 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/d7/1d8acecc4621aa2b70fca28c1a651e02d936152e77d6be07d00601b31cf3/python_multipart-0.0.10-py3-none-any.whl", hash = "sha256:2b06ad9e8d50c7a8db80e3b56dab590137b323410605af2be20d62a5f1ba1dc8", size = 22680 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rich" +version = "13.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/76/40f084cb7db51c9d1fa29a7120717892aeda9a7711f6225692c957a93535/rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a", size = 222080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/11/dadb85e2bd6b1f1ae56669c3e1f0410797f9605d752d68fb47b77f525b31/rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06", size = 241608 }, +] + +[[package]] +name = "ruff" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/7c/3045a526c57cef4b5ec4d5d154692e31429749a49810a53e785de334c4f6/ruff-0.6.7.tar.gz", hash = "sha256:44e52129d82266fa59b587e2cd74def5637b730a69c4542525dfdecfaae38bd5", size = 3073785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/c4/1c5c636f83f905c537785016e9cdd7a36df53c025a2d07940580ecb37bcf/ruff-0.6.7-py3-none-linux_armv6l.whl", hash = "sha256:08277b217534bfdcc2e1377f7f933e1c7957453e8a79764d004e44c40db923f2", size = 10336748 }, + { url = "https://files.pythonhosted.org/packages/84/d9/aa15a56be7ad796f4d7625362aff588f9fc013bbb7323a63571628a2cf2d/ruff-0.6.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c6707a32e03b791f4448dc0dce24b636cbcdee4dd5607adc24e5ee73fd86c00a", size = 9958833 }, + { url = "https://files.pythonhosted.org/packages/27/25/5dd1c32bfc3ad3136c8ebe84312d1bdd2e6c908ac7f60692ec009b7050a8/ruff-0.6.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:533d66b7774ef224e7cf91506a7dafcc9e8ec7c059263ec46629e54e7b1f90ab", size = 9633369 }, + { url = "https://files.pythonhosted.org/packages/0e/3e/01b25484f3cb08fe6fddedf1f55f3f3c0af861a5b5f5082fbe60ab4b2596/ruff-0.6.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a86aac6f915932d259f7bec79173e356165518859f94649d8c50b81ff087e9", size = 10637415 }, + { url = "https://files.pythonhosted.org/packages/8a/c9/5bb9b849e4777e0f961de43edf95d2af0ab34999a5feee957be096887876/ruff-0.6.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3f8822defd260ae2460ea3832b24d37d203c3577f48b055590a426a722d50ef", size = 10097389 }, + { url = "https://files.pythonhosted.org/packages/52/cf/e08f1c290c7d848ddfb2ae811f24f445c18e1d3e50e01c38ffa7f5a50494/ruff-0.6.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba4efe5c6dbbb58be58dd83feedb83b5e95c00091bf09987b4baf510fee5c99", size = 10951440 }, + { url = "https://files.pythonhosted.org/packages/a2/2d/ca8aa0da5841913c302d8034c6de0ce56c401c685184d8dd23cfdd0003f9/ruff-0.6.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:525201b77f94d2b54868f0cbe5edc018e64c22563da6c5c2e5c107a4e85c1c0d", size = 11708900 }, + { url = "https://files.pythonhosted.org/packages/89/fc/9a83c57baee977c82392e19a328b52cebdaf61601af3d99498e278ef5104/ruff-0.6.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8854450839f339e1049fdbe15d875384242b8e85d5c6947bb2faad33c651020b", size = 11258892 }, + { url = "https://files.pythonhosted.org/packages/d3/a3/254cc7afef702c68ae9079290c2a1477ae0e81478589baf745026d8a4eb5/ruff-0.6.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f0b62056246234d59cbf2ea66e84812dc9ec4540518e37553513392c171cb18", size = 12367932 }, + { url = "https://files.pythonhosted.org/packages/9f/55/53f10c1bd8c3b2ae79aed18e62b22c6346f9296aa0ec80489b8442bd06a9/ruff-0.6.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1462fa56c832dc0cea5b4041cfc9c97813505d11cce74ebc6d1aae068de36b", size = 10838629 }, + { url = "https://files.pythonhosted.org/packages/84/72/fb335c2b25432c63d15383ecbd7bfc1915e68cdf8d086a08042052144255/ruff-0.6.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:02b083770e4cdb1495ed313f5694c62808e71764ec6ee5db84eedd82fd32d8f5", size = 10648824 }, + { url = "https://files.pythonhosted.org/packages/92/a8/d57e135a8ad99b6a0c6e2a5c590bcacdd57f44340174f4409c3893368610/ruff-0.6.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c05fd37013de36dfa883a3854fae57b3113aaa8abf5dea79202675991d48624", size = 10174368 }, + { url = "https://files.pythonhosted.org/packages/a7/6f/1a30a6e81dcf2fa9ff3f7011eb87fe76c12a3c6bba74db6a1977d763de1f/ruff-0.6.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f49c9caa28d9bbfac4a637ae10327b3db00f47d038f3fbb2195c4d682e925b14", size = 10514383 }, + { url = "https://files.pythonhosted.org/packages/0b/25/df6f2575bc9fe43a6dedfd8dee12896f09a94303e2c828d5f85856bb69a0/ruff-0.6.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a0e1655868164e114ba43a908fd2d64a271a23660195017c17691fb6355d59bb", size = 10902340 }, + { url = "https://files.pythonhosted.org/packages/68/62/f2c1031e2fb7b94f9bf0603744e73db4ef90081b0eb1b9639a6feefd52ea/ruff-0.6.7-py3-none-win32.whl", hash = "sha256:a939ca435b49f6966a7dd64b765c9df16f1faed0ca3b6f16acdf7731969deb35", size = 8448033 }, + { url = "https://files.pythonhosted.org/packages/97/80/193d1604a3f7d75eb1b2a7ce6bf0fdbdbc136889a65caacea6ffb29501b1/ruff-0.6.7-py3-none-win_amd64.whl", hash = "sha256:590445eec5653f36248584579c06252ad2e110a5d1f32db5420de35fb0e1c977", size = 9273543 }, + { url = "https://files.pythonhosted.org/packages/8e/a8/4abb5a9f58f51e4b1ea386be5ab2e547035bc1ee57200d1eca2f8909a33e/ruff-0.6.7-py3-none-win_arm64.whl", hash = "sha256:b28f0d5e2f771c1fe3c7a45d3f53916fc74a480698c4b5731f0bea61e52137c8", size = 8618044 }, +] + +[[package]] +name = "sentry-sdk" +version = "1.45.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/28/02c0cd9184f9108e3c52519f9628b215077a3854240e0b17ae845e664855/sentry_sdk-1.45.1.tar.gz", hash = "sha256:a16c997c0f4e3df63c0fc5e4207ccb1ab37900433e0f72fef88315d317829a26", size = 244774 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/9f/105366a122efa93f0cb1914f841747d160788e4d022d0488d2d44c2ba26c/sentry_sdk-1.45.1-py2.py3-none-any.whl", hash = "sha256:608887855ccfe39032bfd03936e3a1c4f4fc99b3a4ac49ced54a4220de61c9c1", size = 267163 }, +] + +[package.optional-dependencies] +fastapi = [ + { name = "fastapi" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.35" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/48/4f190a83525f5cefefa44f6adc9e6386c4de5218d686c27eda92eb1f5424/sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f", size = 9562798 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/61/19395d0ae78c94f6f80c8adf39a142f3fe56cfb2235d8f2317d6dae1bf0e/SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b", size = 2090086 }, + { url = "https://files.pythonhosted.org/packages/e6/82/06b5fcbe5d49043e40cf4e01e3b33c471c8d9292d478420b08538cae8928/SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90", size = 2081278 }, + { url = "https://files.pythonhosted.org/packages/68/d1/7fb7ee46949a5fb34005795b1fc06a8fef67587a66da731c14e545f7eb5b/SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea", size = 3063763 }, + { url = "https://files.pythonhosted.org/packages/7e/ff/a1eacd78b31e52a5073e9924fb4722ecc2a72f093ca8181ed81fc61aed2e/SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33", size = 3072032 }, + { url = "https://files.pythonhosted.org/packages/21/ae/ddfecf149a6d16af87408bca7bd108eef7ef23d376cc8464317efb3cea3f/SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9", size = 3028092 }, + { url = "https://files.pythonhosted.org/packages/cc/51/3e84d42121662a160bacd311cfacb29c1e6a229d59dd8edb09caa8ab283b/SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff", size = 3053543 }, + { url = "https://files.pythonhosted.org/packages/3e/7a/039c78105958da3fc361887f0a82c974cb6fa5bba965c1689ec778be1c01/SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b", size = 2062372 }, + { url = "https://files.pythonhosted.org/packages/a2/50/f31e927d32f9729f69d150ffe47e7cf51e3e0bb2148fc400b3e93a92ca4c/SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e", size = 2086485 }, + { url = "https://files.pythonhosted.org/packages/c3/46/9215a35bf98c3a2528e987791e6180eb51624d2c7d5cb8e2d96a6450b657/SQLAlchemy-2.0.35-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60", size = 2091274 }, + { url = "https://files.pythonhosted.org/packages/1e/69/919673c5101a0c633658d58b11b454b251ca82300941fba801201434755d/SQLAlchemy-2.0.35-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62", size = 2081672 }, + { url = "https://files.pythonhosted.org/packages/67/ea/a6b0597cbda12796be2302153369dbbe90573fdab3bc4885f8efac499247/SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6", size = 3200083 }, + { url = "https://files.pythonhosted.org/packages/8c/d6/97bdc8d714fb21762f2092511f380f18cdb2d985d516071fa925bb433a90/SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7", size = 3200080 }, + { url = "https://files.pythonhosted.org/packages/87/d2/8c2adaf2ade4f6f1b725acd0b0be9210bb6a2df41024729a8eec6a86fe5a/SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71", size = 3137108 }, + { url = "https://files.pythonhosted.org/packages/7e/ae/ea05d0bfa8f2b25ae34591895147152854fc950f491c4ce362ae06035db8/SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01", size = 3157437 }, + { url = "https://files.pythonhosted.org/packages/fe/5d/8ad6df01398388a766163d27960b3365f1bbd8bb7b05b5cad321a8b69b25/SQLAlchemy-2.0.35-cp311-cp311-win32.whl", hash = "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e", size = 2061935 }, + { url = "https://files.pythonhosted.org/packages/ff/68/8557efc0c32c8e2c147cb6512237448b8ed594a57cd015fda67f8e56bb3f/SQLAlchemy-2.0.35-cp311-cp311-win_amd64.whl", hash = "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8", size = 2087281 }, + { url = "https://files.pythonhosted.org/packages/2f/2b/fff87e6db0da31212c98bbc445f83fb608ea92b96bda3f3f10e373bac76c/SQLAlchemy-2.0.35-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2", size = 2089790 }, + { url = "https://files.pythonhosted.org/packages/68/92/4bb761bd82764d5827bf6b6095168c40fb5dbbd23670203aef2f96ba6bc6/SQLAlchemy-2.0.35-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468", size = 2080266 }, + { url = "https://files.pythonhosted.org/packages/22/46/068a65db6dc253c6f25a7598d99e0a1d60b14f661f9d09ef6c73c718fa4e/SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d", size = 3229760 }, + { url = "https://files.pythonhosted.org/packages/6e/36/59830dafe40dda592304debd4cd86e583f63472f3a62c9e2695a5795e786/SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db", size = 3240649 }, + { url = "https://files.pythonhosted.org/packages/00/50/844c50c6996f9c7f000c959dd1a7436a6c94e449ee113046a1d19e470089/SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c", size = 3176138 }, + { url = "https://files.pythonhosted.org/packages/df/d2/336b18cac68eecb67de474fc15c85f13be4e615c6f5bae87ea38c6734ce0/SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8", size = 3202753 }, + { url = "https://files.pythonhosted.org/packages/f0/f3/ee1e62fabdc10910b5ef720ae08e59bc785f26652876af3a50b89b97b412/SQLAlchemy-2.0.35-cp312-cp312-win32.whl", hash = "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf", size = 2060113 }, + { url = "https://files.pythonhosted.org/packages/60/63/a3cef44a52979169d884f3583d0640e64b3c28122c096474a1d7cfcaf1f3/SQLAlchemy-2.0.35-cp312-cp312-win_amd64.whl", hash = "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc", size = 2085839 }, + { url = "https://files.pythonhosted.org/packages/0e/c6/33c706449cdd92b1b6d756b247761e27d32230fd6b2de5f44c4c3e5632b2/SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1", size = 1881276 }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/39/8641040ab0d5e1d8a1c2325ae89a01ae659fc96c61a43d158fb71c9a0bf0/sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e", size = 116392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b1/3af5104b716c420e40a6ea1b09886cae3a1b9f4538343875f637755cae5b/sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b", size = 28276 }, +] + +[[package]] +name = "starlette" +version = "0.38.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/b4/e25c3b688ef703d85e55017c6edd0cbf38e5770ab748234363d54ff0251a/starlette-0.38.6.tar.gz", hash = "sha256:863a1588f5574e70a821dadefb41e4881ea451a47a3cd1b4df359d4ffefe5ead", size = 2569491 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/9c/93f7bc03ff03199074e81974cc148908ead60dcf189f68ba1761a0ee35cf/starlette-0.38.6-py3-none-any.whl", hash = "sha256:4517a1409e2e73ee4951214ba012052b9e16f60e90d73cfb06192c19203bbb05", size = 71451 }, +] + +[[package]] +name = "tenacity" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165 }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, +] + +[[package]] +name = "typer" +version = "0.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/58/a79003b91ac2c6890fc5d90145c662fd5771c6f11447f116b63300436bc9/typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722", size = 98953 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/2b/886d13e742e514f704c33c4caa7df0f3b89e5a25ef8db02aa9ca3d9535d5/typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", size = 47288 }, +] + +[[package]] +name = "types-passlib" +version = "1.7.7.20240819" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/19/5041c4bce2909c67fc3f9471ad67972d94c31cb591a970a8faf1220a3717/types-passlib-1.7.7.20240819.tar.gz", hash = "sha256:8fc8df71623845032293d5cf7f8091f0adfeba02d387a2888684b8413f14b3d0", size = 18386 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/4b/606ac25e89908e4577cd1aa19ffbebe55a6720cff69303db68701f3cc388/types_passlib-1.7.7.20240819-py3-none-any.whl", hash = "sha256:c4d299083497b66e12258c7b77c08952574213fdf7009da3135d8181a6a25f23", size = 33240 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "tzdata" +version = "2024.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/5b/e025d02cb3b66b7b76093404392d4b44343c69101cc85f4d180dd5784717/tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", size = 190559 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/58/f9c9e6be752e9fcb8b6a0ee9fb87e6e7a1f6bcab2cdc73f02bb7ba91ada0/tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252", size = 345370 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "uvicorn" +version = "0.30.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/01/5e637e7aa9dd031be5376b9fb749ec20b86f5a5b6a49b87fabd374d5fa9f/uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788", size = 42825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/8e/cdc7d6263db313030e4c257dd5ba3909ebc4e4fb53ad62d5f09b1a2f5458/uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5", size = 62835 }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/f1/dc9577455e011ad43d9379e836ee73f40b4f99c02946849a44f7ae64835e/uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469", size = 2329938 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/69/cc1ad125ea8ce4a4d3ba7d9836062c3fc9063cf163ddf0f168e73f3268e3/uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996", size = 1363922 }, + { url = "https://files.pythonhosted.org/packages/f7/45/5a3f7a32372e4a90dfd83f30507183ec38990b8c5930ed7e36c6a15af47b/uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b", size = 760386 }, + { url = "https://files.pythonhosted.org/packages/9e/a5/9e973b25ade12c938940751bce71d0cb36efee3489014471f7d9c0a3c379/uvloop-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10", size = 3432586 }, + { url = "https://files.pythonhosted.org/packages/a9/e0/0bec8a25b2e9cf14fdfcf0229637b437c923b4e5ca22f8e988363c49bb51/uvloop-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae", size = 3431802 }, + { url = "https://files.pythonhosted.org/packages/95/3b/14cef46dcec6237d858666a4a1fdb171361528c70fcd930bfc312920e7a9/uvloop-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006", size = 4144444 }, + { url = "https://files.pythonhosted.org/packages/9d/5a/0ac516562ff783f760cab3b061f10fdeb4a9f985ad4b44e7e4564ff11691/uvloop-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73", size = 4147039 }, + { url = "https://files.pythonhosted.org/packages/64/bf/45828beccf685b7ed9638d9b77ef382b470c6ca3b5bff78067e02ffd5663/uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037", size = 1320593 }, + { url = "https://files.pythonhosted.org/packages/27/c0/3c24e50bee7802a2add96ca9f0d5eb0ebab07e0a5615539d38aeb89499b9/uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9", size = 736676 }, + { url = "https://files.pythonhosted.org/packages/83/ce/ffa3c72954eae36825acfafd2b6a9221d79abd2670c0d25e04d6ef4a2007/uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e", size = 3494573 }, + { url = "https://files.pythonhosted.org/packages/46/6d/4caab3a36199ba52b98d519feccfcf48921d7a6649daf14a93c7e77497e9/uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756", size = 3489932 }, + { url = "https://files.pythonhosted.org/packages/e4/4f/49c51595bd794945c88613df88922c38076eae2d7653f4624aa6f4980b07/uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0", size = 4185596 }, + { url = "https://files.pythonhosted.org/packages/b8/94/7e256731260d313f5049717d1c4582d52a3b132424c95e16954a50ab95d3/uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf", size = 4185746 }, + { url = "https://files.pythonhosted.org/packages/2d/64/31cbd379d6e260ac8de3f672f904e924f09715c3f192b09f26cc8e9f574c/uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d", size = 1324302 }, + { url = "https://files.pythonhosted.org/packages/1e/6b/9207e7177ff30f78299401f2e1163ea41130d4fd29bcdc6d12572c06b728/uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e", size = 738105 }, + { url = "https://files.pythonhosted.org/packages/c1/ba/b64b10f577519d875992dc07e2365899a1a4c0d28327059ce1e1bdfb6854/uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9", size = 4090658 }, + { url = "https://files.pythonhosted.org/packages/0a/f8/5ceea6876154d926604f10c1dd896adf9bce6d55a55911364337b8a5ed8d/uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab", size = 4173357 }, + { url = "https://files.pythonhosted.org/packages/18/b2/117ab6bfb18274753fbc319607bf06e216bd7eea8be81d5bac22c912d6a7/uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5", size = 4029868 }, + { url = "https://files.pythonhosted.org/packages/6f/52/deb4be09060637ef4752adaa0b75bf770c20c823e8108705792f99cd4a6f/uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00", size = 4115980 }, +] + +[[package]] +name = "virtualenv" +version = "20.26.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/4c/66ce54c8736ff164e85117ca36b02a1e14c042a6963f85eeda82664fda4e/virtualenv-20.26.5.tar.gz", hash = "sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4", size = 9371932 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/1d/e1a44fdd6d30829ba21fc58b5d98a67e7aae8f4165f11d091e53aec12560/virtualenv-20.26.5-py3-none-any.whl", hash = "sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6", size = 5999288 }, +] + +[[package]] +name = "watchfiles" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/27/2ba23c8cc85796e2d41976439b08d52f691655fdb9401362099502d1f0cf/watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1", size = 37870 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/a1/631c12626378b9f1538664aa221feb5c60dfafbd7f60b451f8d0bdbcdedd/watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0", size = 375096 }, + { url = "https://files.pythonhosted.org/packages/f7/5c/f27c979c8a10aaa2822286c1bffdce3db731cd1aa4224b9f86623e94bbfe/watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c", size = 367425 }, + { url = "https://files.pythonhosted.org/packages/74/0d/1889e5649885484d29f6c792ef274454d0a26b20d6ed5fdba5409335ccb6/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361", size = 437705 }, + { url = "https://files.pythonhosted.org/packages/85/8a/01d9a22e839f0d1d547af11b1fcac6ba6f889513f1b2e6f221d9d60d9585/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3", size = 433636 }, + { url = "https://files.pythonhosted.org/packages/62/32/a93db78d340c7ef86cde469deb20e36c6b2a873edee81f610e94bbba4e06/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571", size = 451069 }, + { url = "https://files.pythonhosted.org/packages/99/c2/e9e2754fae3c2721c9a7736f92dab73723f1968ed72535fff29e70776008/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd", size = 469306 }, + { url = "https://files.pythonhosted.org/packages/4c/45/f317d9e3affb06c3c27c478de99f7110143e87f0f001f0f72e18d0e1ddce/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a", size = 476187 }, + { url = "https://files.pythonhosted.org/packages/ac/d3/f1f37248abe0114916921e638f71c7d21fe77e3f2f61750e8057d0b68ef2/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e", size = 425743 }, + { url = "https://files.pythonhosted.org/packages/2b/e8/c7037ea38d838fd81a59cd25761f106ee3ef2cfd3261787bee0c68908171/watchfiles-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c", size = 612327 }, + { url = "https://files.pythonhosted.org/packages/a0/c5/0e6e228aafe01a7995fbfd2a4edb221bb11a2744803b65a5663fb85e5063/watchfiles-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188", size = 595096 }, + { url = "https://files.pythonhosted.org/packages/63/d5/4780e8bf3de3b4b46e7428a29654f7dc041cad6b19fd86d083e4b6f64bbe/watchfiles-0.24.0-cp310-none-win32.whl", hash = "sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735", size = 264149 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/5148898ba55fc9c111a2a4a5fb67ad3fa7eb2b3d7f0618241ed88749313d/watchfiles-0.24.0-cp310-none-win_amd64.whl", hash = "sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04", size = 277542 }, + { url = "https://files.pythonhosted.org/packages/85/02/366ae902cd81ca5befcd1854b5c7477b378f68861597cef854bd6dc69fbe/watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428", size = 375579 }, + { url = "https://files.pythonhosted.org/packages/bc/67/d8c9d256791fe312fea118a8a051411337c948101a24586e2df237507976/watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c", size = 367726 }, + { url = "https://files.pythonhosted.org/packages/b1/dc/a8427b21ef46386adf824a9fec4be9d16a475b850616cfd98cf09a97a2ef/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43", size = 437735 }, + { url = "https://files.pythonhosted.org/packages/3a/21/0b20bef581a9fbfef290a822c8be645432ceb05fb0741bf3c032e0d90d9a/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327", size = 433644 }, + { url = "https://files.pythonhosted.org/packages/1c/e8/d5e5f71cc443c85a72e70b24269a30e529227986096abe091040d6358ea9/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5", size = 450928 }, + { url = "https://files.pythonhosted.org/packages/61/ee/bf17f5a370c2fcff49e1fec987a6a43fd798d8427ea754ce45b38f9e117a/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61", size = 469072 }, + { url = "https://files.pythonhosted.org/packages/a3/34/03b66d425986de3fc6077e74a74c78da298f8cb598887f664a4485e55543/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15", size = 475517 }, + { url = "https://files.pythonhosted.org/packages/70/eb/82f089c4f44b3171ad87a1b433abb4696f18eb67292909630d886e073abe/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823", size = 425480 }, + { url = "https://files.pythonhosted.org/packages/53/20/20509c8f5291e14e8a13104b1808cd7cf5c44acd5feaecb427a49d387774/watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab", size = 612322 }, + { url = "https://files.pythonhosted.org/packages/df/2b/5f65014a8cecc0a120f5587722068a975a692cadbe9fe4ea56b3d8e43f14/watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec", size = 595094 }, + { url = "https://files.pythonhosted.org/packages/18/98/006d8043a82c0a09d282d669c88e587b3a05cabdd7f4900e402250a249ac/watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d", size = 264191 }, + { url = "https://files.pythonhosted.org/packages/8a/8b/badd9247d6ec25f5f634a9b3d0d92e39c045824ec7e8afcedca8ee52c1e2/watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c", size = 277527 }, + { url = "https://files.pythonhosted.org/packages/af/19/35c957c84ee69d904299a38bae3614f7cede45f07f174f6d5a2f4dbd6033/watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633", size = 266253 }, + { url = "https://files.pythonhosted.org/packages/35/82/92a7bb6dc82d183e304a5f84ae5437b59ee72d48cee805a9adda2488b237/watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a", size = 374137 }, + { url = "https://files.pythonhosted.org/packages/87/91/49e9a497ddaf4da5e3802d51ed67ff33024597c28f652b8ab1e7c0f5718b/watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370", size = 367733 }, + { url = "https://files.pythonhosted.org/packages/0d/d8/90eb950ab4998effea2df4cf3a705dc594f6bc501c5a353073aa990be965/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6", size = 437322 }, + { url = "https://files.pythonhosted.org/packages/6c/a2/300b22e7bc2a222dd91fce121cefa7b49aa0d26a627b2777e7bdfcf1110b/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b", size = 433409 }, + { url = "https://files.pythonhosted.org/packages/99/44/27d7708a43538ed6c26708bcccdde757da8b7efb93f4871d4cc39cffa1cc/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e", size = 452142 }, + { url = "https://files.pythonhosted.org/packages/b0/ec/c4e04f755be003129a2c5f3520d2c47026f00da5ecb9ef1e4f9449637571/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea", size = 469414 }, + { url = "https://files.pythonhosted.org/packages/c5/4e/cdd7de3e7ac6432b0abf282ec4c1a1a2ec62dfe423cf269b86861667752d/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f", size = 472962 }, + { url = "https://files.pythonhosted.org/packages/27/69/e1da9d34da7fc59db358424f5d89a56aaafe09f6961b64e36457a80a7194/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234", size = 425705 }, + { url = "https://files.pythonhosted.org/packages/e8/c1/24d0f7357be89be4a43e0a656259676ea3d7a074901f47022f32e2957798/watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef", size = 612851 }, + { url = "https://files.pythonhosted.org/packages/c7/af/175ba9b268dec56f821639c9893b506c69fd999fe6a2e2c51de420eb2f01/watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968", size = 594868 }, + { url = "https://files.pythonhosted.org/packages/44/81/1f701323a9f70805bc81c74c990137123344a80ea23ab9504a99492907f8/watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444", size = 264109 }, + { url = "https://files.pythonhosted.org/packages/b4/0b/32cde5bc2ebd9f351be326837c61bdeb05ad652b793f25c91cac0b48a60b/watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896", size = 277055 }, + { url = "https://files.pythonhosted.org/packages/4b/81/daade76ce33d21dbec7a15afd7479de8db786e5f7b7d249263b4ea174e08/watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418", size = 266169 }, + { url = "https://files.pythonhosted.org/packages/30/dc/6e9f5447ae14f645532468a84323a942996d74d5e817837a5c8ce9d16c69/watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48", size = 373764 }, + { url = "https://files.pythonhosted.org/packages/79/c0/c3a9929c372816c7fc87d8149bd722608ea58dc0986d3ef7564c79ad7112/watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90", size = 367873 }, + { url = "https://files.pythonhosted.org/packages/2e/11/ff9a4445a7cfc1c98caf99042df38964af12eed47d496dd5d0d90417349f/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94", size = 438381 }, + { url = "https://files.pythonhosted.org/packages/48/a3/763ba18c98211d7bb6c0f417b2d7946d346cdc359d585cc28a17b48e964b/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e", size = 432809 }, + { url = "https://files.pythonhosted.org/packages/30/4c/616c111b9d40eea2547489abaf4ffc84511e86888a166d3a4522c2ba44b5/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827", size = 451801 }, + { url = "https://files.pythonhosted.org/packages/b6/be/d7da83307863a422abbfeb12903a76e43200c90ebe5d6afd6a59d158edea/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df", size = 468886 }, + { url = "https://files.pythonhosted.org/packages/1d/d3/3dfe131ee59d5e90b932cf56aba5c996309d94dafe3d02d204364c23461c/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab", size = 472973 }, + { url = "https://files.pythonhosted.org/packages/42/6c/279288cc5653a289290d183b60a6d80e05f439d5bfdfaf2d113738d0f932/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f", size = 425282 }, + { url = "https://files.pythonhosted.org/packages/d6/d7/58afe5e85217e845edf26d8780c2d2d2ae77675eeb8d1b8b8121d799ce52/watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b", size = 612540 }, + { url = "https://files.pythonhosted.org/packages/6d/d5/b96eeb9fe3fda137200dd2f31553670cbc731b1e13164fd69b49870b76ec/watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18", size = 593625 }, + { url = "https://files.pythonhosted.org/packages/c1/e5/c326fe52ee0054107267608d8cea275e80be4455b6079491dfd9da29f46f/watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07", size = 263899 }, + { url = "https://files.pythonhosted.org/packages/a6/8b/8a7755c5e7221bb35fe4af2dc44db9174f90ebf0344fd5e9b1e8b42d381e/watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366", size = 276622 }, + { url = "https://files.pythonhosted.org/packages/df/94/1ad200e937ec91b2a9d6b39ae1cf9c2b1a9cc88d5ceb43aa5c6962eb3c11/watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f", size = 376986 }, + { url = "https://files.pythonhosted.org/packages/ee/fd/d9e020d687ccf90fe95efc513fbb39a8049cf5a3ff51f53c59fcf4c47a5d/watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b", size = 369445 }, + { url = "https://files.pythonhosted.org/packages/43/cb/c0279b35053555d10ef03559c5aebfcb0c703d9c70a7b4e532df74b9b0e8/watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4", size = 439383 }, + { url = "https://files.pythonhosted.org/packages/8b/c4/08b3c2cda45db5169148a981c2100c744a4a222fa7ae7644937c0c002069/watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a", size = 426804 }, +] + +[[package]] +name = "websockets" +version = "13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/73/9223dbc7be3dcaf2a7bbf756c351ec8da04b1fa573edaf545b95f6b0c7fd/websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878", size = 158549 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/94/d15dbfc6a5eb636dbc754303fba18208f2e88cf97e733e1d64fb9cb5c89e/websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee", size = 157815 }, + { url = "https://files.pythonhosted.org/packages/30/02/c04af33f4663945a26f5e8cf561eb140c35452b50af47a83c3fbcfe62ae1/websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7", size = 155466 }, + { url = "https://files.pythonhosted.org/packages/35/e8/719f08d12303ea643655e52d9e9851b2dadbb1991d4926d9ce8862efa2f5/websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6", size = 155716 }, + { url = "https://files.pythonhosted.org/packages/91/e1/14963ae0252a8925f7434065d25dcd4701d5e281a0b4b460a3b5963d2594/websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b", size = 164806 }, + { url = "https://files.pythonhosted.org/packages/ec/fa/ab28441bae5e682a0f7ddf3d03440c0c352f930da419301f4a717f675ef3/websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa", size = 163810 }, + { url = "https://files.pythonhosted.org/packages/44/77/dea187bd9d16d4b91566a2832be31f99a40d0f5bfa55eeb638eb2c3bc33d/websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700", size = 164125 }, + { url = "https://files.pythonhosted.org/packages/cf/d9/3af14544e83f1437eb684b399e6ba0fa769438e869bf5d83d74bc197fae8/websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c", size = 164532 }, + { url = "https://files.pythonhosted.org/packages/1c/8a/6d332eabe7d59dfefe4b8ba6f46c8c5fabb15b71c8a8bc3d2b65de19a7b6/websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0", size = 163948 }, + { url = "https://files.pythonhosted.org/packages/1a/91/a0aeadbaf3017467a1ee03f8fb67accdae233fe2d5ad4b038c0a84e357b0/websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f", size = 163898 }, + { url = "https://files.pythonhosted.org/packages/71/31/a90fb47c63e0ae605be914b0b969d7c6e6ffe2038cd744798e4b3fbce53b/websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe", size = 158706 }, + { url = "https://files.pythonhosted.org/packages/93/ca/9540a9ba80da04dc7f36d790c30cae4252589dbd52ccdc92e75b0be22437/websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a", size = 159141 }, + { url = "https://files.pythonhosted.org/packages/b2/f0/cf0b8a30d86b49e267ac84addbebbc7a48a6e7bb7c19db80f62411452311/websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19", size = 157813 }, + { url = "https://files.pythonhosted.org/packages/bf/e7/22285852502e33071a8cf0ac814f8988480ec6db4754e067b8b9d0e92498/websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5", size = 155469 }, + { url = "https://files.pythonhosted.org/packages/68/d4/c8c7c1e5b40ee03c5cc235955b0fb1ec90e7e37685a5f69229ad4708dcde/websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd", size = 155717 }, + { url = "https://files.pythonhosted.org/packages/c9/e4/c50999b9b848b1332b07c7fd8886179ac395cb766fda62725d1539e7bc6c/websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02", size = 165379 }, + { url = "https://files.pythonhosted.org/packages/bc/49/4a4ad8c072f18fd79ab127650e47b160571aacfc30b110ee305ba25fffc9/websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7", size = 164376 }, + { url = "https://files.pythonhosted.org/packages/af/9b/8c06d425a1d5a74fd764dd793edd02be18cf6fc3b1ccd1f29244ba132dc0/websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096", size = 164753 }, + { url = "https://files.pythonhosted.org/packages/d5/5b/0acb5815095ff800b579ffc38b13ab1b915b317915023748812d24e0c1ac/websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084", size = 165051 }, + { url = "https://files.pythonhosted.org/packages/30/93/c3891c20114eacb1af09dedfcc620c65c397f4fd80a7009cd12d9457f7f5/websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3", size = 164489 }, + { url = "https://files.pythonhosted.org/packages/28/09/af9e19885539759efa2e2cd29b8b3f9eecef7ecefea40d46612f12138b36/websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9", size = 164438 }, + { url = "https://files.pythonhosted.org/packages/b6/08/6f38b8e625b3d93de731f1d248cc1493327f16cb45b9645b3e791782cff0/websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f", size = 158710 }, + { url = "https://files.pythonhosted.org/packages/fb/39/ec8832ecb9bb04a8d318149005ed8cee0ba4e0205835da99e0aa497a091f/websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557", size = 159137 }, + { url = "https://files.pythonhosted.org/packages/df/46/c426282f543b3c0296cf964aa5a7bb17e984f58dde23460c3d39b3148fcf/websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc", size = 157821 }, + { url = "https://files.pythonhosted.org/packages/aa/85/22529867010baac258da7c45848f9415e6cf37fef00a43856627806ffd04/websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49", size = 155480 }, + { url = "https://files.pythonhosted.org/packages/29/2c/bdb339bfbde0119a6e84af43ebf6275278698a2241c2719afc0d8b0bdbf2/websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd", size = 155715 }, + { url = "https://files.pythonhosted.org/packages/9f/d0/8612029ea04c5c22bf7af2fd3d63876c4eaeef9b97e86c11972a43aa0e6c/websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0", size = 165647 }, + { url = "https://files.pythonhosted.org/packages/56/04/1681ed516fa19ca9083f26d3f3a302257e0911ba75009533ed60fbb7b8d1/websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6", size = 164592 }, + { url = "https://files.pythonhosted.org/packages/38/6f/a96417a49c0ed132bb6087e8e39a37db851c70974f5c724a4b2a70066996/websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9", size = 165012 }, + { url = "https://files.pythonhosted.org/packages/40/8b/fccf294919a1b37d190e86042e1a907b8f66cff2b61e9befdbce03783e25/websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68", size = 165311 }, + { url = "https://files.pythonhosted.org/packages/c1/61/f8615cf7ce5fe538476ab6b4defff52beb7262ff8a73d5ef386322d9761d/websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14", size = 164692 }, + { url = "https://files.pythonhosted.org/packages/5c/f1/a29dd6046d3a722d26f182b783a7997d25298873a14028c4760347974ea3/websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf", size = 164686 }, + { url = "https://files.pythonhosted.org/packages/0f/99/ab1cdb282f7e595391226f03f9b498f52109d25a2ba03832e21614967dfa/websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c", size = 158712 }, + { url = "https://files.pythonhosted.org/packages/46/93/e19160db48b5581feac8468330aa11b7292880a94a37d7030478596cc14e/websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3", size = 159145 }, + { url = "https://files.pythonhosted.org/packages/51/20/2b99ca918e1cbd33c53db2cace5f0c0cd8296fc77558e1908799c712e1cd/websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6", size = 157828 }, + { url = "https://files.pythonhosted.org/packages/b8/47/0932a71d3d9c0e9483174f60713c84cee58d62839a143f21a2bcdbd2d205/websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708", size = 155487 }, + { url = "https://files.pythonhosted.org/packages/a9/60/f1711eb59ac7a6c5e98e5637fef5302f45b6f76a2c9d64fd83bbb341377a/websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418", size = 155721 }, + { url = "https://files.pythonhosted.org/packages/6a/e6/ba9a8db7f9d9b0e5f829cf626ff32677f39824968317223605a6b419d445/websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a", size = 165609 }, + { url = "https://files.pythonhosted.org/packages/c1/22/4ec80f1b9c27a0aebd84ccd857252eda8418ab9681eb571b37ca4c5e1305/websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f", size = 164556 }, + { url = "https://files.pythonhosted.org/packages/27/ac/35f423cb6bb15600438db80755609d27eda36d4c0b3c9d745ea12766c45e/websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5", size = 164993 }, + { url = "https://files.pythonhosted.org/packages/31/4e/98db4fd267f8be9e52e86b6ee4e9aa7c42b83452ea0ea0672f176224b977/websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135", size = 165360 }, + { url = "https://files.pythonhosted.org/packages/3f/15/3f0de7cda70ffc94b7e7024544072bc5b26e2c1eb36545291abb755d8cdb/websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2", size = 164745 }, + { url = "https://files.pythonhosted.org/packages/a1/6e/66b6b756aebbd680b934c8bdbb6dcb9ce45aad72cde5f8a7208dbb00dd36/websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6", size = 164732 }, + { url = "https://files.pythonhosted.org/packages/35/c6/12e3aab52c11aeb289e3dbbc05929e7a9d90d7a9173958477d3ef4f8ce2d/websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d", size = 158709 }, + { url = "https://files.pythonhosted.org/packages/41/d8/63d6194aae711d7263df4498200c690a9c39fb437ede10f3e157a6343e0d/websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2", size = 159144 }, + { url = "https://files.pythonhosted.org/packages/2d/75/6da22cb3ad5b8c606963f9a5f9f88656256fecc29d420b4b2bf9e0c7d56f/websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238", size = 155499 }, + { url = "https://files.pythonhosted.org/packages/c0/ba/22833d58629088fcb2ccccedfae725ac0bbcd713319629e97125b52ac681/websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5", size = 155737 }, + { url = "https://files.pythonhosted.org/packages/95/54/61684fe22bdb831e9e1843d972adadf359cf04ab8613285282baea6a24bb/websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9", size = 157095 }, + { url = "https://files.pythonhosted.org/packages/fc/f5/6652fb82440813822022a9301a30afde85e5ff3fb2aebb77f34aabe2b4e8/websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6", size = 156701 }, + { url = "https://files.pythonhosted.org/packages/67/33/ae82a7b860fa8a08aba68818bdf7ff61f04598aa5ab96df4cd5a3e418ca4/websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a", size = 156654 }, + { url = "https://files.pythonhosted.org/packages/63/0b/a1b528d36934f833e20f6da1032b995bf093d55cb416b9f2266f229fb237/websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23", size = 159192 }, + { url = "https://files.pythonhosted.org/packages/56/27/96a5cd2626d11c8280656c6c71d8ab50fe006490ef9971ccd154e0c42cd2/websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f", size = 152134 }, +] diff --git a/copier.yml b/copier.yml index 5db3891c8d..0b5d3428d0 100644 --- a/copier.yml +++ b/copier.yml @@ -63,7 +63,6 @@ _exclude: # Global - .vscode - .mypy_cache - - poetry.lock # Python - __pycache__ - app.egg-info @@ -71,7 +70,6 @@ _exclude: - .mypy_cache - .coverage - htmlcov - - poetry.lock - .cache - .venv # Frontend diff --git a/development.md b/development.md index ad1cd75cbc..77d0b18e76 100644 --- a/development.md +++ b/development.md @@ -134,10 +134,10 @@ You can find a file `.pre-commit-config.yaml` with configurations at the root of After having the `pre-commit` tool installed and available, you need to "install" it in the local repository, so that it runs automatically before each commit. -Using Poetry, you could do it with: +Using `uv`, you could do it with: ```bash -❯ poetry run pre-commit install +❯ uv run pre-commit install pre-commit installed at .git/hooks/pre-commit ``` @@ -153,10 +153,10 @@ Then you can `git add` the modified/fixed files again and now you can commit. #### Running pre-commit hooks manually -you can also run `pre-commit` manually on all the files, you can do it using Poetry with: +you can also run `pre-commit` manually on all the files, you can do it using `uv` with: ```bash -❯ poetry run pre-commit run --all-files +❯ uv run pre-commit run --all-files check for added large files..............................................Passed check toml...............................................................Passed check yaml...............................................................Passed From 775d9acb58fc2676e58c3775e76a2bab5aa4fbcf Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 14:11:09 +0000 Subject: [PATCH 667/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 32b4894603..c270bbc945 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* ⬆️ Migrate from Poetry to uv. PR [#1356](https://github.com/fastapi/full-stack-fastapi-template/pull/1356) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove logic for development dependencies and Jupyter, it was never documented, and I no longer use that trick. PR [#1355](https://github.com/fastapi/full-stack-fastapi-template/pull/1355) by [@tiangolo](https://github.com/tiangolo). * ♻️ Use Docker Compose `watch`. PR [#1354](https://github.com/fastapi/full-stack-fastapi-template/pull/1354) by [@tiangolo](https://github.com/tiangolo). * 🔧 Use plain base official Python Docker image. PR [#1351](https://github.com/fastapi/full-stack-fastapi-template/pull/1351) by [@tiangolo](https://github.com/tiangolo). From 8e310caaca87028f8caa089a93606c584ba2f5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Sep 2024 18:53:08 +0200 Subject: [PATCH 668/771] =?UTF-8?q?=F0=9F=92=A1=20Add=20comments=20to=20Do?= =?UTF-8?q?ckerfile=20with=20uv=20references=20(#1357)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index 08f21fcc66..3c1531788e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -19,6 +19,7 @@ ENV UV_COMPILE_BYTECODE=1 ENV UV_LINK_MODE=copy # Install dependencies +# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ @@ -32,6 +33,8 @@ COPY ./pyproject.toml ./uv.lock ./alembic.ini /app/ COPY ./app /app/app +# Sync the project +# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers RUN --mount=type=cache,target=/root/.cache/uv \ uv sync From b3e6c36c7bfe27f695a1f7d0ee02f1e5ebeb5bcf Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 16:53:28 +0000 Subject: [PATCH 669/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index c270bbc945..4cb756e692 100644 --- a/release-notes.md +++ b/release-notes.md @@ -28,6 +28,7 @@ ### Docs +* 💡 Add comments to Dockerfile with uv references. PR [#1357](https://github.com/fastapi/full-stack-fastapi-template/pull/1357) by [@tiangolo](https://github.com/tiangolo). * 📝 Add Email Templates to `backend/README.md`. PR [#1311](https://github.com/fastapi/full-stack-fastapi-template/pull/1311) by [@alejsdev](https://github.com/alejsdev). ### Internal From 98da2b476d5e25955427a38b1d414f5de997ada5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Sep 2024 21:35:18 +0200 Subject: [PATCH 670/771] =?UTF-8?q?=F0=9F=91=B7=20Add=20GitHub=20Actions?= =?UTF-8?q?=20workflow=20to=20lint=20backend=20apart=20from=20tests=20(#13?= =?UTF-8?q?58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lint-backend.yml | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/lint-backend.yml diff --git a/.github/workflows/lint-backend.yml b/.github/workflows/lint-backend.yml new file mode 100644 index 0000000000..f07d7ca451 --- /dev/null +++ b/.github/workflows/lint-backend.yml @@ -0,0 +1,40 @@ +name: Lint Backend + +on: + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + +jobs: + + lint-backend: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install uv + uses: astral-sh/setup-uv@v2 + with: + version: "0.4.15" + - run: uv run bash scripts/lint.sh + working-directory: backend + + # https://github.com/marketplace/actions/alls-green#why + lint-backend-alls-green: # This job does nothing and is only used for the branch protection + if: always() + needs: + - lint-backend + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} From 1e1307ab084843b04377b5315ff4fb6818bbb9c0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 19:35:36 +0000 Subject: [PATCH 671/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 4cb756e692..9325f9fe21 100644 --- a/release-notes.md +++ b/release-notes.md @@ -33,6 +33,7 @@ ### Internal +* 👷 Add GitHub Actions workflow to lint backend apart from tests. PR [#1358](https://github.com/fastapi/full-stack-fastapi-template/pull/1358) by [@tiangolo](https://github.com/tiangolo). * 👷 Improve playwright CI job. PR [#1335](https://github.com/fastapi/full-stack-fastapi-template/pull/1335) by [@patrick91](https://github.com/patrick91). * 👷 Update `issue-manager.yml`. PR [#1329](https://github.com/fastapi/full-stack-fastapi-template/pull/1329) by [@tiangolo](https://github.com/tiangolo). * 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#1327](https://github.com/fastapi/full-stack-fastapi-template/pull/1327) by [@svlandeg](https://github.com/svlandeg). From 717bb2dfb0179f5d71eddf13034e14488919e212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Sep 2024 22:09:11 +0200 Subject: [PATCH 672/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20settings?= =?UTF-8?q?=20to=20use=20top=20level=20`.env`=20file=20(#1359)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/core/config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 2a4e9fd0ba..2370469d7a 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -25,7 +25,10 @@ def parse_cors(v: Any) -> list[str] | str: class Settings(BaseSettings): model_config = SettingsConfigDict( - env_file=".env", env_ignore_empty=True, extra="ignore" + # Use top level .env file (one level above ./backend/) + env_file="../.env", + env_ignore_empty=True, + extra="ignore", ) API_V1_STR: str = "/api/v1" SECRET_KEY: str = secrets.token_urlsafe(32) From 4f61016cef84555d6cf1b8552b29517bc564eb4b Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 20:09:35 +0000 Subject: [PATCH 673/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 9325f9fe21..e8516a18d3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ ### Refactors +* ♻️ Update settings to use top level `.env` file. PR [#1359](https://github.com/fastapi/full-stack-fastapi-template/pull/1359) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Migrate from Poetry to uv. PR [#1356](https://github.com/fastapi/full-stack-fastapi-template/pull/1356) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove logic for development dependencies and Jupyter, it was never documented, and I no longer use that trick. PR [#1355](https://github.com/fastapi/full-stack-fastapi-template/pull/1355) by [@tiangolo](https://github.com/tiangolo). * ♻️ Use Docker Compose `watch`. PR [#1354](https://github.com/fastapi/full-stack-fastapi-template/pull/1354) by [@tiangolo](https://github.com/tiangolo). From 19505a2bca2a3cbe96ede39ac7f2c41c560169e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Sep 2024 22:36:14 +0200 Subject: [PATCH 674/771] =?UTF-8?q?=F0=9F=94=A8=20Update=20`generate-clien?= =?UTF-8?q?t.sh`=20script,=20make=20it=20fail=20on=20errors,=20fix=20gener?= =?UTF-8?q?ation=20(#1360)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate-client.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/generate-client.sh b/scripts/generate-client.sh index 1327ee6fd1..f7a564fdf0 100644 --- a/scripts/generate-client.sh +++ b/scripts/generate-client.sh @@ -1,6 +1,11 @@ #! /usr/bin/env bash -PYTHONPATH=backend python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()))" > openapi.json +set -e +set -x + +cd backend +python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()))" > ../openapi.json +cd .. node frontend/modify-openapi-operationids.js mv openapi.json frontend/ cd frontend From 8b4b5b27615a927598ab71c68d871ff56bb24948 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 20:36:34 +0000 Subject: [PATCH 675/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index e8516a18d3..63fc875d9c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -34,6 +34,7 @@ ### Internal +* 🔨 Update `generate-client.sh` script, make it fail on errors, fix generation. PR [#1360](https://github.com/fastapi/full-stack-fastapi-template/pull/1360) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Actions workflow to lint backend apart from tests. PR [#1358](https://github.com/fastapi/full-stack-fastapi-template/pull/1358) by [@tiangolo](https://github.com/tiangolo). * 👷 Improve playwright CI job. PR [#1335](https://github.com/fastapi/full-stack-fastapi-template/pull/1335) by [@patrick91](https://github.com/patrick91). * 👷 Update `issue-manager.yml`. PR [#1329](https://github.com/fastapi/full-stack-fastapi-template/pull/1329) by [@tiangolo](https://github.com/tiangolo). From 5109511736bc477deb083b900d0586e27762b9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Sep 2024 23:27:21 +0200 Subject: [PATCH 676/771] =?UTF-8?q?=F0=9F=91=B7=20Run=20tests=20from=20Pyt?= =?UTF-8?q?hon=20environment=20(with=20`uv`),=20not=20from=20Docker=20cont?= =?UTF-8?q?ainer=20(#1361)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/smokeshow.yml | 2 +- .../workflows/{test.yml => test-backend.yml} | 22 +++++++++------- .github/workflows/test-docker-compose.yml | 26 +++++++++++++++++++ 3 files changed, 40 insertions(+), 10 deletions(-) rename .github/workflows/{test.yml => test-backend.yml} (70%) create mode 100644 .github/workflows/test-docker-compose.yml diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index 62aef248ec..90c73f5265 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -2,7 +2,7 @@ name: Smokeshow on: workflow_run: - workflows: [Test] + workflows: [Test Backend] types: [completed] jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test-backend.yml similarity index 70% rename from .github/workflows/test.yml rename to .github/workflows/test-backend.yml index 1874f6886b..4d01336a4a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test-backend.yml @@ -1,4 +1,4 @@ -name: Test +name: Test Backend on: push: @@ -11,7 +11,7 @@ on: jobs: - test: + test-backend: runs-on: ubuntu-latest steps: - name: Checkout @@ -21,14 +21,18 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.10' - - - run: docker compose build + - name: Install uv + uses: astral-sh/setup-uv@v2 + with: + version: "0.4.15" - run: docker compose down -v --remove-orphans - - run: docker compose up -d - - name: Lint - run: docker compose exec -T backend bash scripts/lint.sh + - run: docker compose up -d db mailcatcher + - name: Migrate DB + run: uv run bash scripts/prestart.sh + working-directory: backend - name: Run tests - run: docker compose exec -T backend bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" + run: uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" + working-directory: backend - run: docker compose down -v --remove-orphans - name: Store coverage files uses: actions/upload-artifact@v4 @@ -41,7 +45,7 @@ jobs: alls-green: # This job does nothing and is only used for the branch protection if: always() needs: - - test + - test-backend runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed diff --git a/.github/workflows/test-docker-compose.yml b/.github/workflows/test-docker-compose.yml new file mode 100644 index 0000000000..d8a392342a --- /dev/null +++ b/.github/workflows/test-docker-compose.yml @@ -0,0 +1,26 @@ +name: Test Docker Compose + +on: + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + +jobs: + + test-docker-compose: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - run: docker compose build + - run: docker compose down -v --remove-orphans + - run: docker compose up -d --wait + - name: Test backend is up + run: curl http://localhost:8000/api/v1/utils/health-check + - name: Test frontend is up + run: curl http://localhost:5173 + - run: docker compose down -v --remove-orphans From dfec6d553a898808236382fe363a04306bb9894c Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 21:27:39 +0000 Subject: [PATCH 677/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 63fc875d9c..355021ca0c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -34,6 +34,7 @@ ### Internal +* 👷 Run tests from Python environment (with `uv`), not from Docker container. PR [#1361](https://github.com/fastapi/full-stack-fastapi-template/pull/1361) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update `generate-client.sh` script, make it fail on errors, fix generation. PR [#1360](https://github.com/fastapi/full-stack-fastapi-template/pull/1360) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Actions workflow to lint backend apart from tests. PR [#1358](https://github.com/fastapi/full-stack-fastapi-template/pull/1358) by [@tiangolo](https://github.com/tiangolo). * 👷 Improve playwright CI job. PR [#1335](https://github.com/fastapi/full-stack-fastapi-template/pull/1335) by [@patrick91](https://github.com/patrick91). From 96d420925be72811227258589e127849441e94b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Sep 2024 00:05:29 +0200 Subject: [PATCH 678/771] =?UTF-8?q?=F0=9F=91=B7=20Use=20`uv`=20for=20Pytho?= =?UTF-8?q?n=20env=20to=20generate=20client=20(#1362)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/generate-client.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index a81f78cb51..598140fa8a 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -22,11 +22,18 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.10' + - name: Install uv + uses: astral-sh/setup-uv@v2 + with: + version: "0.4.15" - name: Install dependencies run: npm ci working-directory: frontend - - run: pip install ./backend - - run: bash scripts/generate-client.sh + - run: uv sync + working-directory: backend + - run: uv run bash scripts/generate-client.sh + env: + VIRTUAL_ENV: backend/.venv - name: Commit changes run: | git config --local user.email "github-actions@github.com" @@ -46,4 +53,3 @@ jobs: uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} - \ No newline at end of file From 1ed5cfca81d4e1cb63dd7e406ffbb56724eea817 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 22:05:47 +0000 Subject: [PATCH 679/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 355021ca0c..e9695140d4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -34,6 +34,7 @@ ### Internal +* 👷 Use `uv` for Python env to generate client. PR [#1362](https://github.com/fastapi/full-stack-fastapi-template/pull/1362) by [@tiangolo](https://github.com/tiangolo). * 👷 Run tests from Python environment (with `uv`), not from Docker container. PR [#1361](https://github.com/fastapi/full-stack-fastapi-template/pull/1361) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update `generate-client.sh` script, make it fail on errors, fix generation. PR [#1360](https://github.com/fastapi/full-stack-fastapi-template/pull/1360) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Actions workflow to lint backend apart from tests. PR [#1358](https://github.com/fastapi/full-stack-fastapi-template/pull/1358) by [@tiangolo](https://github.com/tiangolo). From 521deaf466448e22fc07baab1cf41ccb892f1d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Sep 2024 00:29:10 +0200 Subject: [PATCH 680/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20GitHub=20Action?= =?UTF-8?q?s=20format=20(#1363)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/dependabot.yml | 12 ++++++------ .github/workflows/generate-client.yml | 14 +------------- .github/workflows/latest-changes.yml | 10 +++++----- .github/workflows/lint-backend.yml | 15 +-------------- .github/workflows/smokeshow.yml | 5 +---- .github/workflows/test-backend.yml | 16 +--------------- 6 files changed, 15 insertions(+), 57 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cd972a0ba4..854069dfa6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,16 +1,16 @@ version: 2 updates: # GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: - interval: "daily" + interval: daily commit-message: prefix: ⬆ # Python - - package-ecosystem: "pip" - directory: "/" + - package-ecosystem: pip + directory: / schedule: - interval: "daily" + interval: daily commit-message: prefix: ⬆ diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index 598140fa8a..faeebad852 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -21,7 +21,7 @@ jobs: node-version: lts/* - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: "3.10" - name: Install uv uses: astral-sh/setup-uv@v2 with: @@ -41,15 +41,3 @@ jobs: git add frontend/src/client git diff --staged --quiet || git commit -m "✨ Autogenerate frontend client" git push - - # https://github.com/marketplace/actions/alls-green#why - generate-client-alls-green: # This job does nothing and is only used for the branch protection - if: always() - needs: - - generate-client - runs-on: ubuntu-latest - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index b41f961e41..2762cc22a3 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -12,9 +12,9 @@ on: description: PR number required: true debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)" required: false - default: 'false' + default: "false" jobs: latest-changes: @@ -34,7 +34,7 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: ./release-notes.md - latest_changes_header: '## Latest Changes' - end_regex: '^## ' + latest_changes_header: "## Latest Changes" + end_regex: "^## " debug_logs: true - label_header_prefix: '### ' + label_header_prefix: "### " diff --git a/.github/workflows/lint-backend.yml b/.github/workflows/lint-backend.yml index f07d7ca451..4e1c93afc7 100644 --- a/.github/workflows/lint-backend.yml +++ b/.github/workflows/lint-backend.yml @@ -10,7 +10,6 @@ on: - synchronize jobs: - lint-backend: runs-on: ubuntu-latest steps: @@ -19,22 +18,10 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: "3.10" - name: Install uv uses: astral-sh/setup-uv@v2 with: version: "0.4.15" - run: uv run bash scripts/lint.sh working-directory: backend - - # https://github.com/marketplace/actions/alls-green#why - lint-backend-alls-green: # This job does nothing and is only used for the branch protection - if: always() - needs: - - lint-backend - runs-on: ubuntu-latest - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index 90c73f5265..d322e9d8a9 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -16,17 +16,14 @@ jobs: steps: - uses: actions/setup-python@v5 with: - python-version: '3.9' - + python-version: "3.10" - run: pip install smokeshow - - uses: actions/download-artifact@v4 with: name: coverage-html path: backend/htmlcov github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - - run: smokeshow upload backend/htmlcov env: SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 4d01336a4a..cf66734fb9 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -10,17 +10,15 @@ on: - synchronize jobs: - test-backend: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: "3.10" - name: Install uv uses: astral-sh/setup-uv@v2 with: @@ -40,15 +38,3 @@ jobs: name: coverage-html path: backend/htmlcov include-hidden-files: true - - # https://github.com/marketplace/actions/alls-green#why - alls-green: # This job does nothing and is only used for the branch protection - if: always() - needs: - - test-backend - runs-on: ubuntu-latest - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} From d55066ff5198932cd0a36851c1b8792c0b1df6ed Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 23 Sep 2024 22:29:32 +0000 Subject: [PATCH 681/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index e9695140d4..d6c4f81e17 100644 --- a/release-notes.md +++ b/release-notes.md @@ -34,6 +34,7 @@ ### Internal +* 👷 Update GitHub Actions format. PR [#1363](https://github.com/fastapi/full-stack-fastapi-template/pull/1363) by [@tiangolo](https://github.com/tiangolo). * 👷 Use `uv` for Python env to generate client. PR [#1362](https://github.com/fastapi/full-stack-fastapi-template/pull/1362) by [@tiangolo](https://github.com/tiangolo). * 👷 Run tests from Python environment (with `uv`), not from Docker container. PR [#1361](https://github.com/fastapi/full-stack-fastapi-template/pull/1361) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update `generate-client.sh` script, make it fail on errors, fix generation. PR [#1360](https://github.com/fastapi/full-stack-fastapi-template/pull/1360) by [@tiangolo](https://github.com/tiangolo). From 47375c37b446de495cf8dca93c0aa3993c35d4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Sep 2024 11:52:42 +0200 Subject: [PATCH 682/771] =?UTF-8?q?=F0=9F=91=B7=20Use=20uv=20cache=20on=20?= =?UTF-8?q?GitHub=20Actions=20(#1366)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/generate-client.yml | 1 + .github/workflows/lint-backend.yml | 1 + .github/workflows/test-backend.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index faeebad852..b181f908e9 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -26,6 +26,7 @@ jobs: uses: astral-sh/setup-uv@v2 with: version: "0.4.15" + enable-cache: true - name: Install dependencies run: npm ci working-directory: frontend diff --git a/.github/workflows/lint-backend.yml b/.github/workflows/lint-backend.yml index 4e1c93afc7..af1cea642f 100644 --- a/.github/workflows/lint-backend.yml +++ b/.github/workflows/lint-backend.yml @@ -23,5 +23,6 @@ jobs: uses: astral-sh/setup-uv@v2 with: version: "0.4.15" + enable-cache: true - run: uv run bash scripts/lint.sh working-directory: backend diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index cf66734fb9..5abe3c9a3a 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -23,6 +23,7 @@ jobs: uses: astral-sh/setup-uv@v2 with: version: "0.4.15" + enable-cache: true - run: docker compose down -v --remove-orphans - run: docker compose up -d db mailcatcher - name: Migrate DB From 0bf08cb922e98125dbcbb738270ad428ac7b8e7e Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 24 Sep 2024 09:53:03 +0000 Subject: [PATCH 683/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index d6c4f81e17..607f5d9bc1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -34,6 +34,7 @@ ### Internal +* 👷 Use uv cache on GitHub Actions. PR [#1366](https://github.com/fastapi/full-stack-fastapi-template/pull/1366) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Actions format. PR [#1363](https://github.com/fastapi/full-stack-fastapi-template/pull/1363) by [@tiangolo](https://github.com/tiangolo). * 👷 Use `uv` for Python env to generate client. PR [#1362](https://github.com/fastapi/full-stack-fastapi-template/pull/1362) by [@tiangolo](https://github.com/tiangolo). * 👷 Run tests from Python environment (with `uv`), not from Docker container. PR [#1361](https://github.com/fastapi/full-stack-fastapi-template/pull/1361) by [@tiangolo](https://github.com/tiangolo). From f6fcd76b451d7cf508cd950e3f6598bb7a8c34f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 27 Sep 2024 15:30:04 +0200 Subject: [PATCH 684/771] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.7.?= =?UTF-8?q?1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/release-notes.md b/release-notes.md index 607f5d9bc1..6473482d91 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,14 @@ ## Latest Changes -* 👷 Do not sync labels as it overrides manually added labels. PR [#1307](https://github.com/fastapi/full-stack-fastapi-template/pull/1307) by [@tiangolo](https://github.com/tiangolo). +## 0.7.1 + +### Highlights + +* Migrate from Poetry to [`uv`](https://github.com/astral-sh/uv). +* Simplifications and improvements for Docker Compose files, Traefik Dockerfiles. +* Make the API use its own domain `api.example.com` and the frontend use `dashboard.example.com`. This would make it easier to deploy them separately if you needed that. +* The backend and frontend on Docker Compose now listen on the same port as the local development servers, this way you can stop the Docker Compose services and run the local development servers without changing the frontend configuration. ### Features @@ -34,6 +41,7 @@ ### Internal +* 👷 Do not sync labels as it overrides manually added labels. PR [#1307](https://github.com/fastapi/full-stack-fastapi-template/pull/1307) by [@tiangolo](https://github.com/tiangolo). * 👷 Use uv cache on GitHub Actions. PR [#1366](https://github.com/fastapi/full-stack-fastapi-template/pull/1366) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Actions format. PR [#1363](https://github.com/fastapi/full-stack-fastapi-template/pull/1363) by [@tiangolo](https://github.com/tiangolo). * 👷 Use `uv` for Python env to generate client. PR [#1362](https://github.com/fastapi/full-stack-fastapi-template/pull/1362) by [@tiangolo](https://github.com/tiangolo). From 7b89eceabc3b1d099949255b148fa686123872d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 27 Sep 2024 16:23:23 +0200 Subject: [PATCH 685/771] =?UTF-8?q?=F0=9F=91=B7=20Generate=20and=20commit?= =?UTF-8?q?=20client=20only=20on=20same=20repo=20PRs,=20on=20forks,=20show?= =?UTF-8?q?=20the=20error=20(#1376)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/generate-client.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index b181f908e9..827195f859 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -12,7 +12,11 @@ jobs: contents: write runs-on: ubuntu-latest steps: + # For PRs from forks - uses: actions/checkout@v4 + # For PRs from the same repo + - uses: actions/checkout@v4 + if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) with: ref: ${{ github.head_ref }} token: ${{ secrets.FULL_STACK_FASTAPI_TEMPLATE_REPO_TOKEN }} @@ -35,10 +39,19 @@ jobs: - run: uv run bash scripts/generate-client.sh env: VIRTUAL_ENV: backend/.venv - - name: Commit changes + - name: Add changes to git run: | git config --local user.email "github-actions@github.com" git config --local user.name "github-actions" git add frontend/src/client + # Same repo PRs + - name: Push changes + if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) + run: | git diff --staged --quiet || git commit -m "✨ Autogenerate frontend client" git push + # Fork PRs + - name: Check changes + if: ( github.event_name == 'pull_request' && github.secret_source != 'Actions' ) + run: | + git diff --staged --quiet || echo "Changes detected in generated client, run scripts/generate-client.sh and commit the changes" From 66b3be8e8786e80503d7f7d162af726f16e3236d Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 14:23:43 +0000 Subject: [PATCH 686/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 6473482d91..2846c0ef42 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Internal + +* 👷 Generate and commit client only on same repo PRs, on forks, show the error. PR [#1376](https://github.com/fastapi/full-stack-fastapi-template/pull/1376) by [@tiangolo](https://github.com/tiangolo). + ## 0.7.1 ### Highlights From b762b1bec8d63360039e82a343e785d7415784d0 Mon Sep 17 00:00:00 2001 From: Hani Sabsoob <39838955+sebhani@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:41:17 -0400 Subject: [PATCH 687/771] =?UTF-8?q?=F0=9F=92=A1=20Remove=20unnecessary=20c?= =?UTF-8?q?omment=20(#1260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- backend/app/core/db.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/app/core/db.py b/backend/app/core/db.py index d260a856d2..ba991fb36d 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -18,7 +18,6 @@ def init_db(session: Session) -> None: # the tables un-commenting the next lines # from sqlmodel import SQLModel - # from app.core.engine import engine # This works because the models are already imported and registered from app.models # SQLModel.metadata.create_all(engine) From b8a0dfff96d2edeff307ed240d819f940a09fc75 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 15:41:36 +0000 Subject: [PATCH 688/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 2846c0ef42..ac085f59a0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Refactors + +* 💡 Remove unnecessary comment. PR [#1260](https://github.com/fastapi/full-stack-fastapi-template/pull/1260) by [@sebhani](https://github.com/sebhani). + ### Internal * 👷 Generate and commit client only on same repo PRs, on forks, show the error. PR [#1376](https://github.com/fastapi/full-stack-fastapi-template/pull/1376) by [@tiangolo](https://github.com/tiangolo). From f59061f350dfef27b2d7f3346268724ae0edbb32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 27 Sep 2024 17:53:31 +0200 Subject: [PATCH 689/771] =?UTF-8?q?=F0=9F=91=B7=20Tweak=20generate=20clien?= =?UTF-8?q?t=20to=20error=20out=20if=20there=20are=20errors=20(#1377)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/generate-client.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index 827195f859..0b10eb24c9 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -54,4 +54,4 @@ jobs: - name: Check changes if: ( github.event_name == 'pull_request' && github.secret_source != 'Actions' ) run: | - git diff --staged --quiet || echo "Changes detected in generated client, run scripts/generate-client.sh and commit the changes" + git diff --staged --quiet || (echo "Changes detected in generated client, run scripts/generate-client.sh and commit the changes" && exit 1) From 605d7d65d2151cb2d608d50a268c51f98ec49196 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 15:53:52 +0000 Subject: [PATCH 690/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index ac085f59a0..aba214f5f1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ ### Internal +* 👷 Tweak generate client to error out if there are errors. PR [#1377](https://github.com/fastapi/full-stack-fastapi-template/pull/1377) by [@tiangolo](https://github.com/tiangolo). * 👷 Generate and commit client only on same repo PRs, on forks, show the error. PR [#1376](https://github.com/fastapi/full-stack-fastapi-template/pull/1376) by [@tiangolo](https://github.com/tiangolo). ## 0.7.1 From a96b7f3e561194cf7064804fa37e3c44a2bbe02d Mon Sep 17 00:00:00 2001 From: Waket Zheng Date: Fri, 27 Sep 2024 23:59:54 +0800 Subject: [PATCH 691/771] =?UTF-8?q?=F0=9F=94=A7=20Run=20task=20by=20the=20?= =?UTF-8?q?same=20Python=20environment=20used=20to=20run=20Copier=20(#1157?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- copier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copier.yml b/copier.yml index 0b5d3428d0..f98e3fc861 100644 --- a/copier.yml +++ b/copier.yml @@ -97,4 +97,4 @@ _exclude: _answers_file: .copier/.copier-answers.yml _tasks: - - "python .copier/update_dotenv.py" + - ["{{ _copier_python }}", .copier/update_dotenv.py] From 5a4da9e48ecc56b927c6c32b85a7aef888541262 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 16:00:15 +0000 Subject: [PATCH 692/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index aba214f5f1..4126484e99 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ ### Internal +* 🔧 Run task by the same Python environment used to run Copier. PR [#1157](https://github.com/fastapi/full-stack-fastapi-template/pull/1157) by [@waketzheng](https://github.com/waketzheng). * 👷 Tweak generate client to error out if there are errors. PR [#1377](https://github.com/fastapi/full-stack-fastapi-template/pull/1377) by [@tiangolo](https://github.com/tiangolo). * 👷 Generate and commit client only on same repo PRs, on forks, show the error. PR [#1376](https://github.com/fastapi/full-stack-fastapi-template/pull/1376) by [@tiangolo](https://github.com/tiangolo). From 26088cad048f88781df99ae0963c93c896883111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 27 Sep 2024 18:10:59 +0200 Subject: [PATCH 693/771] =?UTF-8?q?=F0=9F=94=A7=20Add=20`ENV=20PYTHONUNBUF?= =?UTF-8?q?FERED=3D1`=20to=20log=20output=20directly=20to=20Docker=20(#137?= =?UTF-8?q?8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index 3c1531788e..9d6e699f30 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,7 @@ FROM python:3.10 +ENV PYTHONUNBUFFERED=1 + WORKDIR /app/ # Install uv From b9db0358feea4fd17328315ccb3c0a64023510e0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 16:11:18 +0000 Subject: [PATCH 694/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 4126484e99..66d4b5d63f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Refactors +* 🔧 Add `ENV PYTHONUNBUFFERED=1` to log output directly to Docker. PR [#1378](https://github.com/fastapi/full-stack-fastapi-template/pull/1378) by [@tiangolo](https://github.com/tiangolo). * 💡 Remove unnecessary comment. PR [#1260](https://github.com/fastapi/full-stack-fastapi-template/pull/1260) by [@sebhani](https://github.com/sebhani). ### Internal From 130adc90a273f315c9732fbebedfd01f0375c3c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:27:40 +0200 Subject: [PATCH 695/771] =?UTF-8?q?=E2=AC=86=20Bump=20tiangolo/issue-manag?= =?UTF-8?q?er=20from=200.5.0=20to=200.5.1=20(#1332)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [tiangolo/issue-manager](https://github.com/tiangolo/issue-manager) from 0.5.0 to 0.5.1. - [Release notes](https://github.com/tiangolo/issue-manager/releases) - [Commits](https://github.com/tiangolo/issue-manager/compare/0.5.0...0.5.1) --- updated-dependencies: - dependency-name: tiangolo/issue-manager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue-manager.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index b50a77f185..156983e317 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -27,7 +27,7 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: tiangolo/issue-manager@0.5.0 + - uses: tiangolo/issue-manager@0.5.1 with: token: ${{ secrets.GITHUB_TOKEN }} config: > From b9dbef1d0a4badda24c8d52b0cac30596460d94a Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 16:28:02 +0000 Subject: [PATCH 696/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 66d4b5d63f..71dbbee81e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ ### Internal +* ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#1332](https://github.com/fastapi/full-stack-fastapi-template/pull/1332) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Run task by the same Python environment used to run Copier. PR [#1157](https://github.com/fastapi/full-stack-fastapi-template/pull/1157) by [@waketzheng](https://github.com/waketzheng). * 👷 Tweak generate client to error out if there are errors. PR [#1377](https://github.com/fastapi/full-stack-fastapi-template/pull/1377) by [@tiangolo](https://github.com/tiangolo). * 👷 Generate and commit client only on same repo PRs, on forks, show the error. PR [#1376](https://github.com/fastapi/full-stack-fastapi-template/pull/1376) by [@tiangolo](https://github.com/tiangolo). From bf8cea943250697b68f7d3a19b62d7060b6e278f Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 27 Sep 2024 18:30:09 +0200 Subject: [PATCH 697/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20pre-commit=20en?= =?UTF-8?q?d-of-file-fixer=20hook=20to=20exclude=20email-templates=20(#129?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .pre-commit-config.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c717bc01a1..512da0105c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,11 @@ repos: args: - --unsafe - id: end-of-file-fixer - exclude: ^frontend/src/client/.* + exclude: | + (?x)^( + frontend/src/client/.*| + backend/app/email-templates/build/.* + )$ - id: trailing-whitespace exclude: ^frontend/src/client/.* - repo: https://github.com/charliermarsh/ruff-pre-commit From 5db8a2b055d83d61a7d54660a4c94254b41f8690 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 16:30:27 +0000 Subject: [PATCH 698/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 71dbbee81e..3db8b43d90 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ ### Internal +* 👷 Update pre-commit end-of-file-fixer hook to exclude email-templates. PR [#1296](https://github.com/fastapi/full-stack-fastapi-template/pull/1296) by [@goabonga](https://github.com/goabonga). * ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#1332](https://github.com/fastapi/full-stack-fastapi-template/pull/1332) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Run task by the same Python environment used to run Copier. PR [#1157](https://github.com/fastapi/full-stack-fastapi-template/pull/1157) by [@waketzheng](https://github.com/waketzheng). * 👷 Tweak generate client to error out if there are errors. PR [#1377](https://github.com/fastapi/full-stack-fastapi-template/pull/1377) by [@tiangolo](https://github.com/tiangolo). From ce03c8835ec5968c567ed7a646ffbd8dd450c866 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:39:19 +0200 Subject: [PATCH 699/771] =?UTF-8?q?=E2=AC=86=20Bump=20astral-sh/setup-uv?= =?UTF-8?q?=20from=202=20to=203=20(#1364)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 2 to 3. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v2...v3) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/generate-client.yml | 2 +- .github/workflows/lint-backend.yml | 2 +- .github/workflows/test-backend.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index 0b10eb24c9..1cf417ab89 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -27,7 +27,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v2 + uses: astral-sh/setup-uv@v3 with: version: "0.4.15" enable-cache: true diff --git a/.github/workflows/lint-backend.yml b/.github/workflows/lint-backend.yml index af1cea642f..c7d7f2f8e4 100644 --- a/.github/workflows/lint-backend.yml +++ b/.github/workflows/lint-backend.yml @@ -20,7 +20,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v2 + uses: astral-sh/setup-uv@v3 with: version: "0.4.15" enable-cache: true diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 5abe3c9a3a..2244836a01 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -20,7 +20,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v2 + uses: astral-sh/setup-uv@v3 with: version: "0.4.15" enable-cache: true From de2dbdcd9b778d8e4e96393b5f5b96c8b0ec88e8 Mon Sep 17 00:00:00 2001 From: Hmily <961532186@qq.com> Date: Sat, 28 Sep 2024 00:39:40 +0800 Subject: [PATCH 700/771] =?UTF-8?q?=F0=9F=94=8A=20Enable=20logging=20for?= =?UTF-8?q?=20email=20utils=20by=20default=20(#1374)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- backend/app/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/app/utils.py b/backend/app/utils.py index 267993745e..38cff81333 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -11,6 +11,9 @@ from app.core.config import settings +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + @dataclass class EmailData: @@ -48,7 +51,7 @@ def send_email( if settings.SMTP_PASSWORD: smtp_options["password"] = settings.SMTP_PASSWORD response = message.send(to=email_to, smtp=smtp_options) - logging.info(f"send email result: {response}") + logger.info(f"send email result: {response}") def generate_test_email(email_to: str) -> EmailData: From 09f793ae5fbcf679a5a545a4e886bfc9ac386b2c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 16:39:41 +0000 Subject: [PATCH 701/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 3db8b43d90..ce080b6ac6 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ ### Internal +* ⬆ Bump astral-sh/setup-uv from 2 to 3. PR [#1364](https://github.com/fastapi/full-stack-fastapi-template/pull/1364) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update pre-commit end-of-file-fixer hook to exclude email-templates. PR [#1296](https://github.com/fastapi/full-stack-fastapi-template/pull/1296) by [@goabonga](https://github.com/goabonga). * ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#1332](https://github.com/fastapi/full-stack-fastapi-template/pull/1332) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Run task by the same Python environment used to run Copier. PR [#1157](https://github.com/fastapi/full-stack-fastapi-template/pull/1157) by [@waketzheng](https://github.com/waketzheng). From fe28a53e73b3ee56c4e63356fddcd1fdd2fd3937 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 16:40:03 +0000 Subject: [PATCH 702/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index ce080b6ac6..c1a8104b11 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Refactors +* 🔊 Enable logging for email utils by default. PR [#1374](https://github.com/fastapi/full-stack-fastapi-template/pull/1374) by [@ihmily](https://github.com/ihmily). * 🔧 Add `ENV PYTHONUNBUFFERED=1` to log output directly to Docker. PR [#1378](https://github.com/fastapi/full-stack-fastapi-template/pull/1378) by [@tiangolo](https://github.com/tiangolo). * 💡 Remove unnecessary comment. PR [#1260](https://github.com/fastapi/full-stack-fastapi-template/pull/1260) by [@sebhani](https://github.com/sebhani). From 70784919984a24d4fa69ba963af4c9bf92c7b76e Mon Sep 17 00:00:00 2001 From: Muhammad Sameer Amin <35958006+sameeramin@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:42:36 +0500 Subject: [PATCH 703/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactored=20code?= =?UTF-8?q?=20to=20use=20encryption=20algorithm=20name=20from=20settings?= =?UTF-8?q?=20for=20consistency=20(#1160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- backend/app/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/app/utils.py b/backend/app/utils.py index 38cff81333..ac029f6342 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -9,6 +9,7 @@ from jinja2 import Template from jwt.exceptions import InvalidTokenError +from app.core import security from app.core.config import settings logging.basicConfig(level=logging.INFO) @@ -107,14 +108,16 @@ def generate_password_reset_token(email: str) -> str: encoded_jwt = jwt.encode( {"exp": exp, "nbf": now, "sub": email}, settings.SECRET_KEY, - algorithm="HS256", + algorithm=security.ALGORITHM, ) return encoded_jwt def verify_password_reset_token(token: str) -> str | None: try: - decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) + decoded_token = jwt.decode( + token, settings.SECRET_KEY, algorithms=[security.ALGORITHM] + ) return str(decoded_token["sub"]) except InvalidTokenError: return None From 8b486f8996d031cb52bed04276e14b21976d81c6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 27 Sep 2024 16:42:55 +0000 Subject: [PATCH 704/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index c1a8104b11..553e5d167d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Refactors +* ♻️ Refactored code to use encryption algorithm name from settings for consistency. PR [#1160](https://github.com/fastapi/full-stack-fastapi-template/pull/1160) by [@sameeramin](https://github.com/sameeramin). * 🔊 Enable logging for email utils by default. PR [#1374](https://github.com/fastapi/full-stack-fastapi-template/pull/1374) by [@ihmily](https://github.com/ihmily). * 🔧 Add `ENV PYTHONUNBUFFERED=1` to log output directly to Docker. PR [#1378](https://github.com/fastapi/full-stack-fastapi-template/pull/1378) by [@tiangolo](https://github.com/tiangolo). * 💡 Remove unnecessary comment. PR [#1260](https://github.com/fastapi/full-stack-fastapi-template/pull/1260) by [@sebhani](https://github.com/sebhani). From 7a3852e8dcf2791386c2858d3ab489259852eb74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:39:17 +0200 Subject: [PATCH 705/771] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20rollup=20fr?= =?UTF-8?q?om=204.6.1=20to=204.22.5=20in=20/frontend=20(#1379)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [rollup](https://github.com/rollup/rollup) from 4.6.1 to 4.22.5. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v4.6.1...v4.22.5) --- updated-dependencies: - dependency-name: rollup dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 312 ++++++++++++++++++++++++------------- 1 file changed, 208 insertions(+), 104 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9560ff9f69..5155a31c0a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2150,9 +2150,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", - "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", "cpu": [ "arm" ], @@ -2163,9 +2163,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz", - "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", "cpu": [ "arm64" ], @@ -2176,9 +2176,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz", - "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", "cpu": [ "arm64" ], @@ -2189,9 +2189,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz", - "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", "cpu": [ "x64" ], @@ -2202,9 +2202,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz", - "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", "cpu": [ "arm" ], @@ -2215,9 +2228,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz", - "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", "cpu": [ "arm64" ], @@ -2228,9 +2241,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz", - "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", "cpu": [ "arm64" ], @@ -2240,10 +2253,49 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", - "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", "cpu": [ "x64" ], @@ -2254,9 +2306,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz", - "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", "cpu": [ "x64" ], @@ -2267,9 +2319,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz", - "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", "cpu": [ "arm64" ], @@ -2280,9 +2332,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz", - "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", "cpu": [ "ia32" ], @@ -2293,9 +2345,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz", - "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", "cpu": [ "x64" ], @@ -2663,6 +2715,12 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3647,10 +3705,13 @@ } }, "node_modules/rollup": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", - "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, "bin": { "rollup": "dist/bin/rollup" }, @@ -3659,18 +3720,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.6.1", - "@rollup/rollup-android-arm64": "4.6.1", - "@rollup/rollup-darwin-arm64": "4.6.1", - "@rollup/rollup-darwin-x64": "4.6.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.6.1", - "@rollup/rollup-linux-arm64-gnu": "4.6.1", - "@rollup/rollup-linux-arm64-musl": "4.6.1", - "@rollup/rollup-linux-x64-gnu": "4.6.1", - "@rollup/rollup-linux-x64-musl": "4.6.1", - "@rollup/rollup-win32-arm64-msvc": "4.6.1", - "@rollup/rollup-win32-ia32-msvc": "4.6.1", - "@rollup/rollup-win32-x64-msvc": "4.6.1", + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", "fsevents": "~2.3.2" } }, @@ -5361,86 +5426,114 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@rollup/rollup-android-arm-eabi": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", - "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz", - "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz", - "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz", - "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz", - "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz", - "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz", - "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", - "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz", - "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz", - "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz", - "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz", - "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", "dev": true, "optional": true }, @@ -5625,6 +5718,12 @@ "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.1.3.tgz", "integrity": "sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw==" }, + "@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, "@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -6328,23 +6427,28 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "rollup": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", - "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.6.1", - "@rollup/rollup-android-arm64": "4.6.1", - "@rollup/rollup-darwin-arm64": "4.6.1", - "@rollup/rollup-darwin-x64": "4.6.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.6.1", - "@rollup/rollup-linux-arm64-gnu": "4.6.1", - "@rollup/rollup-linux-arm64-musl": "4.6.1", - "@rollup/rollup-linux-x64-gnu": "4.6.1", - "@rollup/rollup-linux-x64-musl": "4.6.1", - "@rollup/rollup-win32-arm64-msvc": "4.6.1", - "@rollup/rollup-win32-ia32-msvc": "4.6.1", - "@rollup/rollup-win32-x64-msvc": "4.6.1", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", + "@types/estree": "1.0.6", "fsevents": "~2.3.2" } }, From 9eda943f3a55bd829e9535c2309940599a61b4ed Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 30 Sep 2024 13:39:42 +0000 Subject: [PATCH 706/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 553e5d167d..966a0360c1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -11,6 +11,7 @@ ### Internal +* ⬆️ Bump rollup from 4.6.1 to 4.22.5 in /frontend. PR [#1379](https://github.com/fastapi/full-stack-fastapi-template/pull/1379) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump astral-sh/setup-uv from 2 to 3. PR [#1364](https://github.com/fastapi/full-stack-fastapi-template/pull/1364) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update pre-commit end-of-file-fixer hook to exclude email-templates. PR [#1296](https://github.com/fastapi/full-stack-fastapi-template/pull/1296) by [@goabonga](https://github.com/goabonga). * ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#1332](https://github.com/fastapi/full-stack-fastapi-template/pull/1332) by [@dependabot[bot]](https://github.com/apps/dependabot). From 763cbd284f839b7e17a5c02be645383a51c43b59 Mon Sep 17 00:00:00 2001 From: Saltie Date: Thu, 3 Oct 2024 20:32:46 +0200 Subject: [PATCH 707/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20PaginationFo?= =?UTF-8?q?oter=20component=20(#1381)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Common/PaginationFooter.tsx | 36 +++++++++++++++++++ frontend/src/routes/_layout/admin.tsx | 25 +++++-------- frontend/src/routes/_layout/items.tsx | 24 ++++--------- 3 files changed, 51 insertions(+), 34 deletions(-) create mode 100644 frontend/src/components/Common/PaginationFooter.tsx diff --git a/frontend/src/components/Common/PaginationFooter.tsx b/frontend/src/components/Common/PaginationFooter.tsx new file mode 100644 index 0000000000..788246399f --- /dev/null +++ b/frontend/src/components/Common/PaginationFooter.tsx @@ -0,0 +1,36 @@ +import { Button, Flex } from "@chakra-ui/react" + +type PaginationFooterProps = { + hasNextPage?: boolean + hasPreviousPage?: boolean + onChangePage: (newPage: number) => void + page: number +} + +export function PaginationFooter({ + hasNextPage, + hasPreviousPage, + onChangePage, + page, +}: PaginationFooterProps) { + return ( + + + Page {page} + + + ) +} diff --git a/frontend/src/routes/_layout/admin.tsx b/frontend/src/routes/_layout/admin.tsx index 644653ff79..b96fbc3799 100644 --- a/frontend/src/routes/_layout/admin.tsx +++ b/frontend/src/routes/_layout/admin.tsx @@ -1,7 +1,6 @@ import { Badge, Box, - Button, Container, Flex, Heading, @@ -23,6 +22,7 @@ import { type UserPublic, UsersService } from "../../client" import AddUser from "../../components/Admin/AddUser" import ActionsMenu from "../../components/Common/ActionsMenu" import Navbar from "../../components/Common/Navbar" +import { PaginationFooter } from "../../components/Common/PaginationFooter.tsx" const usersSearchSchema = z.object({ page: z.number().catch(1), @@ -128,7 +128,7 @@ function UsersTable() { @@ -137,21 +137,12 @@ function UsersTable() { )} - - - Page {page} - - + ) } diff --git a/frontend/src/routes/_layout/items.tsx b/frontend/src/routes/_layout/items.tsx index 174fa83c9b..9216d3cbb4 100644 --- a/frontend/src/routes/_layout/items.tsx +++ b/frontend/src/routes/_layout/items.tsx @@ -1,7 +1,5 @@ import { - Button, Container, - Flex, Heading, SkeletonText, Table, @@ -21,6 +19,7 @@ import { ItemsService } from "../../client" import ActionsMenu from "../../components/Common/ActionsMenu" import Navbar from "../../components/Common/Navbar" import AddItem from "../../components/Items/AddItem" +import { PaginationFooter } from "../../components/Common/PaginationFooter.tsx" const itemsSearchSchema = z.object({ page: z.number().catch(1), @@ -112,21 +111,12 @@ function ItemsTable() { )} - - - Page {page} - - + ) } From 72cf68d1e0b2ac0a2d0b5a728f178a23f5f49d37 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 3 Oct 2024 18:33:05 +0000 Subject: [PATCH 708/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 966a0360c1..827e54dda9 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Refactors +* ♻️ Add PaginationFooter component. PR [#1381](https://github.com/fastapi/full-stack-fastapi-template/pull/1381) by [@saltie2193](https://github.com/saltie2193). * ♻️ Refactored code to use encryption algorithm name from settings for consistency. PR [#1160](https://github.com/fastapi/full-stack-fastapi-template/pull/1160) by [@sameeramin](https://github.com/sameeramin). * 🔊 Enable logging for email utils by default. PR [#1374](https://github.com/fastapi/full-stack-fastapi-template/pull/1374) by [@ihmily](https://github.com/ihmily). * 🔧 Add `ENV PYTHONUNBUFFERED=1` to log output directly to Docker. PR [#1378](https://github.com/fastapi/full-stack-fastapi-template/pull/1378) by [@tiangolo](https://github.com/tiangolo). From 99ddad0c6f65183d97bfbadbc282c44197374952 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Thu, 3 Oct 2024 20:35:15 +0200 Subject: [PATCH 709/771] =?UTF-8?q?=F0=9F=94=A7=20Add=20.auth=20playwright?= =?UTF-8?q?=20folder=20to=20.gitignore=20(#1383)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/.gitignore b/frontend/.gitignore index dfc4015cce..75e25e0ef4 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -27,3 +27,4 @@ openapi.json /playwright-report/ /blob-report/ /playwright/.cache/ +/playwright/.auth/ \ No newline at end of file From 364d9d9d99e3349df90e3ccc74e09da5dc61407f Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 3 Oct 2024 18:35:51 +0000 Subject: [PATCH 710/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 827e54dda9..9fd87cd750 100644 --- a/release-notes.md +++ b/release-notes.md @@ -12,6 +12,7 @@ ### Internal +* 🔧 Add .auth playwright folder to `.gitignore`. PR [#1383](https://github.com/fastapi/full-stack-fastapi-template/pull/1383) by [@justin-p](https://github.com/justin-p). * ⬆️ Bump rollup from 4.6.1 to 4.22.5 in /frontend. PR [#1379](https://github.com/fastapi/full-stack-fastapi-template/pull/1379) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump astral-sh/setup-uv from 2 to 3. PR [#1364](https://github.com/fastapi/full-stack-fastapi-template/pull/1364) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update pre-commit end-of-file-fixer hook to exclude email-templates. PR [#1296](https://github.com/fastapi/full-stack-fastapi-template/pull/1296) by [@goabonga](https://github.com/goabonga). From c2e747c3471cdd37b3486317cdd5f1c567ccae81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 7 Oct 2024 22:33:45 +0200 Subject: [PATCH 711/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20`labeler.yml`?= =?UTF-8?q?=20(#1388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/labeler.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index c3bb83f9a5..e8e58015a2 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -17,6 +17,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 + if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }} + - run: echo "Done adding labels" # Run this after labeler applied labels check-labels: needs: From 5b1d47708cb15e7d962b28a88139088e66007858 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 7 Oct 2024 20:34:33 +0000 Subject: [PATCH 712/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 9fd87cd750..8f0d86ac37 100644 --- a/release-notes.md +++ b/release-notes.md @@ -12,6 +12,7 @@ ### Internal +* 👷 Update `labeler.yml`. PR [#1388](https://github.com/fastapi/full-stack-fastapi-template/pull/1388) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add .auth playwright folder to `.gitignore`. PR [#1383](https://github.com/fastapi/full-stack-fastapi-template/pull/1383) by [@justin-p](https://github.com/justin-p). * ⬆️ Bump rollup from 4.6.1 to 4.22.5 in /frontend. PR [#1379](https://github.com/fastapi/full-stack-fastapi-template/pull/1379) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump astral-sh/setup-uv from 2 to 3. PR [#1364](https://github.com/fastapi/full-stack-fastapi-template/pull/1364) by [@dependabot[bot]](https://github.com/apps/dependabot). From 4bfeb624f2d65aff7aa77d13e3724a3ec56711ad Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 12 Oct 2024 14:38:29 +0200 Subject: [PATCH 713/771] =?UTF-8?q?=F0=9F=93=9D=20Add=20MailCatcher=20to?= =?UTF-8?q?=20`development.md`=20(#1387)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- development.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index 77d0b18e76..d7d41d73f1 100644 --- a/development.md +++ b/development.md @@ -186,6 +186,8 @@ Adminer: http://localhost:8080 Traefik UI: http://localhost:8090 +MailCatcher: http://localhost:1080 + ### Development URLs with `localhost.tiangolo.com` Configured Development URLs, for local development. @@ -194,10 +196,12 @@ Frontend: http://dashboard.localhost.tiangolo.com Backend: http://api.localhost.tiangolo.com -Automatic Interactive Docs (Swagger UI): http://api.localhost.tiangolo.comdocs +Automatic Interactive Docs (Swagger UI): http://api.localhost.tiangolo.com/docs -Automatic Alternative Docs (ReDoc): http://api.localhost.tiangolo.comredoc +Automatic Alternative Docs (ReDoc): http://api.localhost.tiangolo.com/redoc Adminer: http://localhost.tiangolo.com:8080 Traefik UI: http://localhost.tiangolo.com:8090 + +MailCatcher: http://localhost.tiangolo.com:1080 \ No newline at end of file From e05f520ee7d9e04138ac34ffab4391998b9df216 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 12 Oct 2024 12:38:47 +0000 Subject: [PATCH 714/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 8f0d86ac37..e79d895dce 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,10 @@ * 🔧 Add `ENV PYTHONUNBUFFERED=1` to log output directly to Docker. PR [#1378](https://github.com/fastapi/full-stack-fastapi-template/pull/1378) by [@tiangolo](https://github.com/tiangolo). * 💡 Remove unnecessary comment. PR [#1260](https://github.com/fastapi/full-stack-fastapi-template/pull/1260) by [@sebhani](https://github.com/sebhani). +### Docs + +* 📝 Add MailCatcher to `development.md`. PR [#1387](https://github.com/fastapi/full-stack-fastapi-template/pull/1387) by [@tobiase](https://github.com/tobiase). + ### Internal * 👷 Update `labeler.yml`. PR [#1388](https://github.com/fastapi/full-stack-fastapi-template/pull/1388) by [@tiangolo](https://github.com/tiangolo). From 7faa409d18232fdc23a6f30c7ccad3d98faa344d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 12 Oct 2024 15:54:30 +0200 Subject: [PATCH 715/771] =?UTF-8?q?=F0=9F=91=B7=20Fix=20smokeshow,=20check?= =?UTF-8?q?out=20files=20on=20CI=20(#1395)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/smokeshow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index d322e9d8a9..61fde520ea 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -14,6 +14,7 @@ jobs: statuses: write steps: + - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.10" From bc6c2c9d2d6ee47d42f4090d133d85e97d633e2e Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 12 Oct 2024 13:55:29 +0000 Subject: [PATCH 716/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index e79d895dce..874bea6417 100644 --- a/release-notes.md +++ b/release-notes.md @@ -16,6 +16,7 @@ ### Internal +* 👷 Fix smokeshow, checkout files on CI. PR [#1395](https://github.com/fastapi/full-stack-fastapi-template/pull/1395) by [@tiangolo](https://github.com/tiangolo). * 👷 Update `labeler.yml`. PR [#1388](https://github.com/fastapi/full-stack-fastapi-template/pull/1388) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add .auth playwright folder to `.gitignore`. PR [#1383](https://github.com/fastapi/full-stack-fastapi-template/pull/1383) by [@justin-p](https://github.com/justin-p). * ⬆️ Bump rollup from 4.6.1 to 4.22.5 in /frontend. PR [#1379](https://github.com/fastapi/full-stack-fastapi-template/pull/1379) by [@dependabot[bot]](https://github.com/apps/dependabot). From ebcc2382037d1d0d4d4dbba0c387ccfddf0e3064 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:35:47 +0200 Subject: [PATCH 717/771] =?UTF-8?q?=F0=9F=91=B7=20Update=20issue=20manager?= =?UTF-8?q?=20workflow=20(#1398)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/issue-manager.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index 156983e317..109ac0e989 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -39,5 +39,9 @@ jobs: "waiting": { "delay": 2628000, "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR." + }, + "invalid": { + "delay": 0, + "message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details." } } From 8bcfbea7a0fb688545a54485a66a9ecd9f31abf3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 14 Oct 2024 15:36:28 +0000 Subject: [PATCH 718/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 874bea6417..d2d00fbbbd 100644 --- a/release-notes.md +++ b/release-notes.md @@ -16,6 +16,7 @@ ### Internal +* 👷 Update issue manager workflow. PR [#1398](https://github.com/fastapi/full-stack-fastapi-template/pull/1398) by [@alejsdev](https://github.com/alejsdev). * 👷 Fix smokeshow, checkout files on CI. PR [#1395](https://github.com/fastapi/full-stack-fastapi-template/pull/1395) by [@tiangolo](https://github.com/tiangolo). * 👷 Update `labeler.yml`. PR [#1388](https://github.com/fastapi/full-stack-fastapi-template/pull/1388) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add .auth playwright folder to `.gitignore`. PR [#1383](https://github.com/fastapi/full-stack-fastapi-template/pull/1383) by [@justin-p](https://github.com/justin-p). From 275aba6af9d00ba52331094fe9f82b0fe9048918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 25 Oct 2024 23:56:34 +0200 Subject: [PATCH 719/771] =?UTF-8?q?=F0=9F=91=B7=20Improve=20Playwright=20C?= =?UTF-8?q?I=20speed:=20sharding=20(paralel=20runs),=20run=20in=20Docker?= =?UTF-8?q?=20to=20use=20cache,=20use=20env=20vars=20(#1405)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/playwright.yml | 84 ++++++++++++++++++----- .github/workflows/test-docker-compose.yml | 2 +- docker-compose.override.yml | 25 +++++++ frontend/.env | 1 + frontend/Dockerfile.playwright | 13 ++++ frontend/playwright.config.ts | 5 +- frontend/src/routes/_layout/admin.tsx | 2 +- frontend/src/routes/_layout/items.tsx | 2 +- frontend/tests/reset-password.spec.ts | 8 ++- frontend/tests/utils/mailcatcher.ts | 2 +- frontend/tsconfig.json | 2 +- 11 files changed, 120 insertions(+), 26 deletions(-) create mode 100644 frontend/Dockerfile.playwright diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a884800227..8c741221f7 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -16,10 +16,36 @@ on: default: 'false' jobs: + changes: + runs-on: ubuntu-latest + # Set job outputs to values from filter step + outputs: + changed: ${{ steps.filter.outputs.changed }} + steps: + - uses: actions/checkout@v4 + # For pull requests it's not necessary to checkout the code but for the main branch it is + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + changed: + - backend/** + - frontend/** + - .env + - docker-compose*.yml + - .github/workflows/playwright.yml - test: + test-playwright: + needs: + - changes + if: ${{ needs.changes.outputs.changed == 'true' }} timeout-minutes: 60 runs-on: ubuntu-latest + strategy: + matrix: + shardIndex: [1, 2, 3, 4] + shardTotal: [4] + fail-fast: false steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -33,35 +59,61 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - name: Install dependencies - run: npm ci - working-directory: frontend - - name: Install Playwright Browsers - run: npx playwright install --with-deps - working-directory: frontend - run: docker compose build - run: docker compose down -v --remove-orphans - - run: docker compose up -d --wait backend mailcatcher - name: Run Playwright tests - run: npx playwright test --fail-on-flaky-tests --trace=retain-on-failure - working-directory: frontend + run: docker compose run --rm playwright npx playwright test --fail-on-flaky-tests --trace=retain-on-failure --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - run: docker compose down -v --remove-orphans - - uses: actions/upload-artifact@v4 - if: always() + - name: Upload blob report to GitHub Actions Artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: blob-report-${{ matrix.shardIndex }} + path: frontend/blob-report + include-hidden-files: true + retention-days: 1 + + merge-playwright-reports: + needs: + - test-playwright + - changes + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ !cancelled() && needs.changes.outputs.changed == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: npm ci + working-directory: frontend + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: frontend/all-blob-reports + pattern: blob-report-* + merge-multiple: true + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + working-directory: frontend + - name: Upload HTML report + uses: actions/upload-artifact@v4 with: - name: playwright-report - path: frontend/playwright-report/ + name: html-report--attempt-${{ github.run_attempt }} + path: frontend/playwright-report retention-days: 30 include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why - e2e-alls-green: # This job does nothing and is only used for the branch protection + alls-green-playwright: # This job does nothing and is only used for the branch protection if: always() needs: - - test + - test-playwright runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} + allowed-skips: test-playwright diff --git a/.github/workflows/test-docker-compose.yml b/.github/workflows/test-docker-compose.yml index d8a392342a..17792ede50 100644 --- a/.github/workflows/test-docker-compose.yml +++ b/.github/workflows/test-docker-compose.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v4 - run: docker compose build - run: docker compose down -v --remove-orphans - - run: docker compose up -d --wait + - run: docker compose up -d --wait backend frontend adminer - name: Test backend is up run: curl http://localhost:8000/api/v1/utils/health-check - name: Test frontend is up diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 3792f1f026..0751abe901 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -102,6 +102,31 @@ services: - VITE_API_URL=http://localhost:8000 - NODE_ENV=development + playwright: + build: + context: ./frontend + dockerfile: Dockerfile.playwright + args: + - VITE_API_URL=http://backend:8000 + - NODE_ENV=production + ipc: host + depends_on: + - backend + - mailcatcher + env_file: + - .env + environment: + - VITE_API_URL=http://backend:8000 + - MAILCATCHER_HOST=http://mailcatcher:1080 + # For the reports when run locally + - PLAYWRIGHT_HTML_HOST=0.0.0.0 + - CI=${CI} + volumes: + - ./frontend/blob-report:/app/blob-report + - ./frontend/test-results:/app/test-results + ports: + - 9323:9323 + networks: traefik-public: # For local dev, don't expect an external Traefik network diff --git a/frontend/.env b/frontend/.env index 5934e2e7d2..27fcbfe8c8 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1,2 @@ VITE_API_URL=http://localhost:8000 +MAILCATCHER_HOST=http://localhost:1080 diff --git a/frontend/Dockerfile.playwright b/frontend/Dockerfile.playwright new file mode 100644 index 0000000000..e76ac15f65 --- /dev/null +++ b/frontend/Dockerfile.playwright @@ -0,0 +1,13 @@ +FROM node:20 + +WORKDIR /app + +COPY package*.json /app/ + +RUN npm install + +RUN npx -y playwright install --with-deps + +COPY ./ /app/ + +ARG VITE_API_URL=${VITE_API_URL} diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index dcdd6fec81..b9d5a51246 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -1,11 +1,10 @@ import { defineConfig, devices } from '@playwright/test'; - +import 'dotenv/config' /** * Read environment variables from file. * https://github.com/motdotla/dotenv */ -// require('dotenv').config(); /** * See https://playwright.dev/docs/test-configuration. @@ -21,7 +20,7 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: process.env.CI ? 'blob' : 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ diff --git a/frontend/src/routes/_layout/admin.tsx b/frontend/src/routes/_layout/admin.tsx index b96fbc3799..dde6247896 100644 --- a/frontend/src/routes/_layout/admin.tsx +++ b/frontend/src/routes/_layout/admin.tsx @@ -49,7 +49,7 @@ function UsersTable() { const { page } = Route.useSearch() const navigate = useNavigate({ from: Route.fullPath }) const setPage = (page: number) => - navigate({ search: (prev) => ({ ...prev, page }) }) + navigate({ search: (prev: {[key: string]: string}) => ({ ...prev, page }) }) const { data: users, diff --git a/frontend/src/routes/_layout/items.tsx b/frontend/src/routes/_layout/items.tsx index 9216d3cbb4..93f7ad5048 100644 --- a/frontend/src/routes/_layout/items.tsx +++ b/frontend/src/routes/_layout/items.tsx @@ -45,7 +45,7 @@ function ItemsTable() { const { page } = Route.useSearch() const navigate = useNavigate({ from: Route.fullPath }) const setPage = (page: number) => - navigate({ search: (prev) => ({ ...prev, page }) }) + navigate({ search: (prev: {[key: string]: string}) => ({ ...prev, page }) }) const { data: items, diff --git a/frontend/tests/reset-password.spec.ts b/frontend/tests/reset-password.spec.ts index 88ec798791..94671b84fe 100644 --- a/frontend/tests/reset-password.spec.ts +++ b/frontend/tests/reset-password.spec.ts @@ -50,7 +50,9 @@ test("User can reset password successfully using the link", async ({ timeout: 5000, }) - await page.goto(`http://localhost:1080/messages/${emailData.id}.html`) + await page.goto( + `${process.env.MAILCATCHER_HOST}/messages/${emailData.id}.html`, + ) const selector = 'a[href*="/reset-password?token="]' @@ -103,7 +105,9 @@ test("Weak new password validation", async ({ page, request }) => { timeout: 5000, }) - await page.goto(`http://localhost:1080/messages/${emailData.id}.html`) + await page.goto( + `${process.env.MAILCATCHER_HOST}/messages/${emailData.id}.html`, + ) const selector = 'a[href*="/reset-password?token="]' let url = await page.getAttribute(selector, "href") diff --git a/frontend/tests/utils/mailcatcher.ts b/frontend/tests/utils/mailcatcher.ts index 601ce434fb..049792d0c8 100644 --- a/frontend/tests/utils/mailcatcher.ts +++ b/frontend/tests/utils/mailcatcher.ts @@ -10,7 +10,7 @@ async function findEmail({ request, filter, }: { request: APIRequestContext; filter?: (email: Email) => boolean }) { - const response = await request.get("http://localhost:1080/messages") + const response = await request.get(`${process.env.MAILCATCHER_HOST}/messages`) let emails = await response.json() diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index baadbb9fb1..355a2a920b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -20,6 +20,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src", "*.ts", "**/*.ts"], + "include": ["src/**/*.ts", "tests/**/*.ts", "playwright.config.ts"], "references": [{ "path": "./tsconfig.node.json" }] } From c17236de0fea3330bfe9e6a8cf4f3296f61ba772 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 25 Oct 2024 21:56:53 +0000 Subject: [PATCH 720/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index d2d00fbbbd..07f5522631 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Refactors +* 👷 Improve Playwright CI speed: sharding (paralel runs), run in Docker to use cache, use env vars. PR [#1405](https://github.com/fastapi/full-stack-fastapi-template/pull/1405) by [@tiangolo](https://github.com/tiangolo). * ♻️ Add PaginationFooter component. PR [#1381](https://github.com/fastapi/full-stack-fastapi-template/pull/1381) by [@saltie2193](https://github.com/saltie2193). * ♻️ Refactored code to use encryption algorithm name from settings for consistency. PR [#1160](https://github.com/fastapi/full-stack-fastapi-template/pull/1160) by [@sameeramin](https://github.com/sameeramin). * 🔊 Enable logging for email utils by default. PR [#1374](https://github.com/fastapi/full-stack-fastapi-template/pull/1374) by [@ihmily](https://github.com/ihmily). From dc9dea585629e95b7f16df035c54b3fc774d24bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 1 Nov 2024 16:29:29 +0100 Subject: [PATCH 721/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20`deployment.md`?= =?UTF-8?q?,=20instructions=20to=20install=20GitHub=20Runner=20in=20non-ro?= =?UTF-8?q?ot=20VMs=20(#1412)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployment.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/deployment.md b/deployment.md index 9301f8091b..eadf76ddae 100644 --- a/deployment.md +++ b/deployment.md @@ -183,22 +183,22 @@ There are already two environments configured, `staging` and `production`. 🚀 ### Install GitHub Actions Runner -* On your remote server, if you are running as the `root` user, create a user for your GitHub Actions: +* On your remote server, create a user for your GitHub Actions: ```bash -adduser github +sudo adduser github ``` * Add Docker permissions to the `github` user: ```bash -usermod -aG docker github +sudo usermod -aG docker github ``` * Temporarily switch to the `github` user: ```bash -su - github +sudo su - github ``` * Go to the `github` user's home directory: @@ -219,9 +219,15 @@ To make sure it runs on startup and continues running, you can install it as a s exit ``` -After you do it, you would be on the `root` user again. And you will be on the previous directory, belonging to the `root` user. +After you do it, you will be on the previous user again. And you will be on the previous directory, belonging to that user. -* Go to the `actions-runner` directory inside of the `github` user's home directory: +Before being able to go the `github` user directory, you need to become the `root` user (you might already be): + +```bash +sudo su +``` + +* As the `root` user, go to the `actions-runner` directory inside of the `github` user's home directory: ```bash cd /home/github/actions-runner From 6e38f60e27a1ff0637686bf4ef443405e20693c9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 1 Nov 2024 15:29:47 +0000 Subject: [PATCH 722/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 07f5522631..d712192ebf 100644 --- a/release-notes.md +++ b/release-notes.md @@ -13,6 +13,7 @@ ### Docs +* 📝 Update `deployment.md`, instructions to install GitHub Runner in non-root VMs. PR [#1412](https://github.com/fastapi/full-stack-fastapi-template/pull/1412) by [@tiangolo](https://github.com/tiangolo). * 📝 Add MailCatcher to `development.md`. PR [#1387](https://github.com/fastapi/full-stack-fastapi-template/pull/1387) by [@tobiase](https://github.com/tobiase). ### Internal From fcc587dc83a5644b99b8aa6883ebbc8bea9077b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:02:06 +0000 Subject: [PATCH 723/771] =?UTF-8?q?=E2=AC=86=20Bump=20tiangolo/latest-chan?= =?UTF-8?q?ges=20from=200.3.1=20to=200.3.2=20(#1418)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [tiangolo/latest-changes](https://github.com/tiangolo/latest-changes) from 0.3.1 to 0.3.2. - [Release notes](https://github.com/tiangolo/latest-changes/releases) - [Commits](https://github.com/tiangolo/latest-changes/compare/0.3.1...0.3.2) --- updated-dependencies: - dependency-name: tiangolo/latest-changes dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/latest-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index 2762cc22a3..607c5243b0 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -30,7 +30,7 @@ jobs: with: # To allow latest-changes to commit to the main branch token: ${{ secrets.LATEST_CHANGES }} - - uses: tiangolo/latest-changes@0.3.1 + - uses: tiangolo/latest-changes@0.3.2 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: ./release-notes.md From 5342f9660e1f6e40539b7f0cad5ac5969d4bea04 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Nov 2024 22:02:26 +0000 Subject: [PATCH 724/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index d712192ebf..384620e35c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -18,6 +18,7 @@ ### Internal +* ⬆ Bump tiangolo/latest-changes from 0.3.1 to 0.3.2. PR [#1418](https://github.com/fastapi/full-stack-fastapi-template/pull/1418) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update issue manager workflow. PR [#1398](https://github.com/fastapi/full-stack-fastapi-template/pull/1398) by [@alejsdev](https://github.com/alejsdev). * 👷 Fix smokeshow, checkout files on CI. PR [#1395](https://github.com/fastapi/full-stack-fastapi-template/pull/1395) by [@tiangolo](https://github.com/tiangolo). * 👷 Update `labeler.yml`. PR [#1388](https://github.com/fastapi/full-stack-fastapi-template/pull/1388) by [@tiangolo](https://github.com/tiangolo). From 9e3c3c23420e660ad5a288b64213758dc6baa3d4 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Wed, 27 Nov 2024 12:06:59 +0100 Subject: [PATCH 725/771] =?UTF-8?q?=E2=9C=A8=20Migrate=20to=20latest=20ope?= =?UTF-8?q?napi-ts=20(#1430)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/openapi-ts.config.ts | 15 + frontend/package-lock.json | 1099 +++++++++++++++-- frontend/package.json | 4 +- frontend/src/client/core/ApiRequestOptions.ts | 25 +- frontend/src/client/core/OpenAPI.ts | 14 +- frontend/src/client/core/request.ts | 27 +- frontend/src/client/core/types.ts | 14 - frontend/src/client/index.ts | 10 +- frontend/src/client/models.ts | 99 -- .../src/client/{schemas.ts => schemas.gen.ts} | 243 ++-- .../src/client/{services.ts => sdk.gen.ts} | 506 ++++---- frontend/src/client/types.gen.ts | 221 ++++ 12 files changed, 1703 insertions(+), 574 deletions(-) create mode 100644 frontend/openapi-ts.config.ts delete mode 100644 frontend/src/client/core/types.ts delete mode 100644 frontend/src/client/models.ts rename frontend/src/client/{schemas.ts => schemas.gen.ts} (58%) rename frontend/src/client/{services.ts => sdk.gen.ts} (56%) create mode 100644 frontend/src/client/types.gen.ts diff --git a/frontend/openapi-ts.config.ts b/frontend/openapi-ts.config.ts new file mode 100644 index 0000000000..178d04b3bf --- /dev/null +++ b/frontend/openapi-ts.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "@hey-api/openapi-ts" + +export default defineConfig({ + client: "legacy/axios", + input: "./openapi.json", + output: "./src/client", + // exportSchemas: true, + plugins: [ + { + name: "@hey-api/sdk", + // NOTE: this doesn't allow tree-shaking + asClass: true, + }, + ], +}) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5155a31c0a..661ce88605 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,7 +26,7 @@ }, "devDependencies": { "@biomejs/biome": "1.6.1", - "@hey-api/openapi-ts": "^0.34.1", + "@hey-api/openapi-ts": "^0.57.0", "@playwright/test": "^1.45.2", "@tanstack/router-devtools": "1.19.1", "@tanstack/router-vite-plugin": "1.19.0", @@ -39,6 +39,24 @@ "vite": "^5.0.13" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -2064,66 +2082,36 @@ } }, "node_modules/@hey-api/openapi-ts": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.34.1.tgz", - "integrity": "sha512-7Ak+0nvf4Nhzk04tXGg6h4eM7lnWRgfjCPmMl2MyXrhS5urxd3Bg/PhtpB84u18wnwcM4rIeCUlTwDDQ/OB3NQ==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.57.0.tgz", + "integrity": "sha512-TFcr7CYAFYLJVjJzCNk8bbGpLhn5K7PR3SHvBizVCZM4PdrcbTx6++W7FyKq84TGXuptN70+LvM+8bOSf3PgCw==", "dev": true, + "license": "FSL-1.1-MIT", "dependencies": { - "@apidevtools/json-schema-ref-parser": "11.5.4", - "camelcase": "8.0.0", - "commander": "12.0.0", + "@apidevtools/json-schema-ref-parser": "11.7.2", + "c12": "2.0.1", + "commander": "12.1.0", "handlebars": "4.7.8" }, "bin": { - "openapi-ts": "bin/index.js" + "openapi-ts": "bin/index.cjs" }, "engines": { "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/@hey-api/openapi-ts/node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.5.4", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.5.4.tgz", - "integrity": "sha512-o2fsypTGU0WxRxbax8zQoHiIB4dyrkwYfcm8TxZ+bx9pCzcWZbQtiMqpgBvWA/nJ2TrGjK5adCLfTH8wUeU/Wg==", - "dev": true, - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" }, "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, - "node_modules/@hey-api/openapi-ts/node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "dev": true, - "engines": { - "node": ">=16" + "url": "https://github.com/sponsors/hey-api" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@hey-api/openapi-ts/node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true, - "engines": { - "node": ">=18" + "peerDependencies": { + "typescript": "^5.x" } }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@playwright/test": { "version": "1.45.2", @@ -2725,7 +2713,8 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.14.202", @@ -2816,11 +2805,25 @@ "@zag-js/dom-query": "0.16.0" } }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "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 + "dev": true, + "license": "Python-2.0" }, "node_modules/aria-hidden": { "version": "1.2.3", @@ -2862,6 +2865,35 @@ "npm": ">=6" } }, + "node_modules/c12": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", + "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.1", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^2.3.0", + "mlly": "^1.7.1", + "ohash": "^1.1.4", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2870,6 +2902,42 @@ "node": ">=6" } }, + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/clsx": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", @@ -2895,11 +2963,38 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -2928,6 +3023,21 @@ "node": ">=10" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", @@ -2957,6 +3067,13 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2965,6 +3082,13 @@ "node": ">=0.4.0" } }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -3038,6 +3162,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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, + "license": "MIT", + "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/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -3137,6 +3285,32 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3167,6 +3341,39 @@ "node": ">=6" } }, + "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, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/giget": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "tar": "^6.2.0" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/goober": { "version": "2.1.14", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", @@ -3216,6 +3423,16 @@ "react-is": "^16.7.0" } }, + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3255,6 +3472,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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, + "license": "MIT", + "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, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.0.tgz", + "integrity": "sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3265,6 +3512,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3298,6 +3546,13 @@ "loose-envify": "cli.js" } }, + "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, + "license": "MIT" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3317,6 +3572,19 @@ "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, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -3326,6 +3594,69 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.3.tgz", + "integrity": "sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^1.1.2", + "pkg-types": "^1.2.1", + "ufo": "^1.5.4" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -3350,6 +3681,63 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-fetch-native": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.12.tgz", + "integrity": "sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3358,6 +3746,29 @@ "node": ">=0.10.0" } }, + "node_modules/ohash": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz", + "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "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", @@ -3386,6 +3797,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -3399,12 +3820,38 @@ "node": ">=8" } }, + "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, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "node_modules/pkg-types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", + "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.2", + "pathe": "^1.1.2" + } + }, "node_modules/playwright": { "version": "1.45.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz", @@ -3507,6 +3954,17 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -3675,6 +4133,20 @@ } } }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -3747,6 +4219,42 @@ "loose-envify": "^1.1.0" } }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3765,6 +4273,19 @@ "node": ">=0.10.0" } }, + "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, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -3781,6 +4302,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -3822,6 +4361,13 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -3945,12 +4491,35 @@ } } }, + "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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -3970,6 +4539,17 @@ } }, "dependencies": { + "@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "dev": true, + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + } + }, "@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -5369,40 +5949,15 @@ "optional": true }, "@hey-api/openapi-ts": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.34.1.tgz", - "integrity": "sha512-7Ak+0nvf4Nhzk04tXGg6h4eM7lnWRgfjCPmMl2MyXrhS5urxd3Bg/PhtpB84u18wnwcM4rIeCUlTwDDQ/OB3NQ==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.57.0.tgz", + "integrity": "sha512-TFcr7CYAFYLJVjJzCNk8bbGpLhn5K7PR3SHvBizVCZM4PdrcbTx6++W7FyKq84TGXuptN70+LvM+8bOSf3PgCw==", "dev": true, "requires": { - "@apidevtools/json-schema-ref-parser": "11.5.4", - "camelcase": "8.0.0", - "commander": "12.0.0", + "@apidevtools/json-schema-ref-parser": "11.7.2", + "c12": "2.0.1", + "commander": "12.1.0", "handlebars": "4.7.8" - }, - "dependencies": { - "@apidevtools/json-schema-ref-parser": { - "version": "11.5.4", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.5.4.tgz", - "integrity": "sha512-o2fsypTGU0WxRxbax8zQoHiIB4dyrkwYfcm8TxZ+bx9pCzcWZbQtiMqpgBvWA/nJ2TrGjK5adCLfTH8wUeU/Wg==", - "dev": true, - "requires": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - } - }, - "camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "dev": true - }, - "commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true - } } }, "@jsdevtools/ono": { @@ -5816,6 +6371,12 @@ "@zag-js/dom-query": "0.16.0" } }, + "acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5855,11 +6416,55 @@ "resolve": "^1.19.0" } }, + "c12": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", + "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==", + "dev": true, + "requires": { + "chokidar": "^4.0.1", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^2.3.0", + "mlly": "^1.7.1", + "ohash": "^1.1.4", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.2.0", + "rc9": "^2.1.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, + "chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "requires": { + "readdirp": "^4.0.1" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "requires": { + "consola": "^3.2.3" + } + }, "clsx": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", @@ -5879,11 +6484,29 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true + }, "compute-scroll-into-view": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" }, + "confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true + }, + "consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true + }, "convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -5909,6 +6532,17 @@ "yaml": "^1.10.0" } }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", @@ -5931,11 +6565,23 @@ "@babel/runtime": "^7.21.0" } }, + "defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true + }, "detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -5990,6 +6636,23 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, + "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, + "requires": { + "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" + } + }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -6059,6 +6722,26 @@ } } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6076,6 +6759,28 @@ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" }, + "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 + }, + "giget": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", + "dev": true, + "requires": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "tar": "^6.2.0" + } + }, "goober": { "version": "2.1.14", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", @@ -6112,6 +6817,12 @@ "react-is": "^16.7.0" } }, + "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 + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -6142,6 +6853,24 @@ "hasown": "^2.0.0" } }, + "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 + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jiti": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.0.tgz", + "integrity": "sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6179,6 +6908,12 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "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 + }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -6192,12 +6927,63 @@ "mime-db": "1.52.0" } }, + "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 + }, "minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mlly": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.3.tgz", + "integrity": "sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==", + "dev": true, + "requires": { + "acorn": "^8.14.0", + "pathe": "^1.1.2", + "pkg-types": "^1.2.1", + "ufo": "^1.5.4" + } + }, "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -6210,11 +6996,63 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node-fetch-native": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", + "dev": true + }, + "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, + "requires": { + "path-key": "^4.0.0" + }, + "dependencies": { + "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 + } + } + }, + "nypm": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.12.tgz", + "integrity": "sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==", + "dev": true, + "requires": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, + "ohash": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz", + "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==", + "dev": true + }, + "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, + "requires": { + "mimic-fn": "^4.0.0" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6234,6 +7072,12 @@ "lines-and-columns": "^1.1.6" } }, + "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 + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -6244,12 +7088,35 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "pkg-types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", + "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", + "dev": true, + "requires": { + "confbox": "^0.1.8", + "mlly": "^1.7.2", + "pathe": "^1.1.2" + } + }, "playwright": { "version": "1.45.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz", @@ -6307,6 +7174,16 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "requires": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -6406,6 +7283,12 @@ "tslib": "^2.0.0" } }, + "readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true + }, "regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -6460,6 +7343,27 @@ "loose-envify": "^1.1.0" } }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "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 + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6472,6 +7376,12 @@ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, + "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 + }, "stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -6482,6 +7392,20 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, "tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -6513,6 +7437,12 @@ "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true }, + "ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true + }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -6561,12 +7491,27 @@ "rollup": "^4.2.0" } }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1a7a547f68..84f9841485 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,7 @@ "build": "tsc && vite build", "lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./", "preview": "vite preview", - "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true" + "generate-client": "openapi-ts" }, "dependencies": { "@chakra-ui/icons": "2.1.1", @@ -29,7 +29,7 @@ }, "devDependencies": { "@biomejs/biome": "1.6.1", - "@hey-api/openapi-ts": "^0.34.1", + "@hey-api/openapi-ts": "^0.57.0", "@playwright/test": "^1.45.2", "@tanstack/router-devtools": "1.19.1", "@tanstack/router-vite-plugin": "1.19.0", diff --git a/frontend/src/client/core/ApiRequestOptions.ts b/frontend/src/client/core/ApiRequestOptions.ts index 4cc259284f..d1136f428b 100644 --- a/frontend/src/client/core/ApiRequestOptions.ts +++ b/frontend/src/client/core/ApiRequestOptions.ts @@ -1,20 +1,21 @@ -export type ApiRequestOptions = { +export type ApiRequestOptions = { + readonly body?: any + readonly cookies?: Record + readonly errors?: Record + readonly formData?: Record | any[] | Blob | File + readonly headers?: Record + readonly mediaType?: string readonly method: - | "GET" - | "PUT" - | "POST" | "DELETE" - | "OPTIONS" + | "GET" | "HEAD" + | "OPTIONS" | "PATCH" - readonly url: string + | "POST" + | "PUT" readonly path?: Record - readonly cookies?: Record - readonly headers?: Record readonly query?: Record - readonly formData?: Record - readonly body?: any - readonly mediaType?: string readonly responseHeader?: string - readonly errors?: Record + readonly responseTransformer?: (data: unknown) => Promise + readonly url: string } diff --git a/frontend/src/client/core/OpenAPI.ts b/frontend/src/client/core/OpenAPI.ts index 746df5e61d..e99068ea2e 100644 --- a/frontend/src/client/core/OpenAPI.ts +++ b/frontend/src/client/core/OpenAPI.ts @@ -1,10 +1,9 @@ import type { AxiosRequestConfig, AxiosResponse } from "axios" import type { ApiRequestOptions } from "./ApiRequestOptions" -import type { TResult } from "./types" type Headers = Record type Middleware = (value: T) => T | Promise -type Resolver = (options: ApiRequestOptions) => Promise +type Resolver = (options: ApiRequestOptions) => Promise export class Interceptors { _fns: Middleware[] @@ -13,14 +12,14 @@ export class Interceptors { this._fns = [] } - eject(fn: Middleware) { + eject(fn: Middleware): void { const index = this._fns.indexOf(fn) if (index !== -1) { this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)] } } - use(fn: Middleware) { + use(fn: Middleware): void { this._fns = [...this._fns, fn] } } @@ -31,7 +30,6 @@ export type OpenAPIConfig = { ENCODE_PATH?: ((path: string) => string) | undefined HEADERS?: Headers | Resolver | undefined PASSWORD?: string | Resolver | undefined - RESULT?: TResult TOKEN?: string | Resolver | undefined USERNAME?: string | Resolver | undefined VERSION: string @@ -48,10 +46,12 @@ export const OpenAPI: OpenAPIConfig = { ENCODE_PATH: undefined, HEADERS: undefined, PASSWORD: undefined, - RESULT: "body", TOKEN: undefined, USERNAME: undefined, VERSION: "0.1.0", WITH_CREDENTIALS: false, - interceptors: { request: new Interceptors(), response: new Interceptors() }, + interceptors: { + request: new Interceptors(), + response: new Interceptors(), + }, } diff --git a/frontend/src/client/core/request.ts b/frontend/src/client/core/request.ts index 99d38b46f1..8b42272b93 100644 --- a/frontend/src/client/core/request.ts +++ b/frontend/src/client/core/request.ts @@ -54,7 +54,9 @@ export const getQueryString = (params: Record): string => { return } - if (Array.isArray(value)) { + if (value instanceof Date) { + append(key, value.toISOString()) + } else if (Array.isArray(value)) { value.forEach((v) => encodePair(key, v)) } else if (typeof value === "object") { Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)) @@ -113,10 +115,10 @@ export const getFormData = ( return undefined } -type Resolver = (options: ApiRequestOptions) => Promise +type Resolver = (options: ApiRequestOptions) => Promise export const resolve = async ( - options: ApiRequestOptions, + options: ApiRequestOptions, resolver?: T | Resolver, ): Promise => { if (typeof resolver === "function") { @@ -125,14 +127,18 @@ export const resolve = async ( return resolver } -export const getHeaders = async ( +export const getHeaders = async ( config: OpenAPIConfig, - options: ApiRequestOptions, + options: ApiRequestOptions, ): Promise> => { const [token, username, password, additionalHeaders] = await Promise.all([ + // @ts-ignore resolve(options, config.TOKEN), + // @ts-ignore resolve(options, config.USERNAME), + // @ts-ignore resolve(options, config.PASSWORD), + // @ts-ignore resolve(options, config.HEADERS), ]) @@ -187,7 +193,7 @@ export const getRequestBody = (options: ApiRequestOptions): unknown => { export const sendRequest = async ( config: OpenAPIConfig, - options: ApiRequestOptions, + options: ApiRequestOptions, url: string, body: unknown, formData: FormData | undefined, @@ -325,7 +331,7 @@ export const catchErrorCodes = ( */ export const request = ( config: OpenAPIConfig, - options: ApiRequestOptions, + options: ApiRequestOptions, axiosClient: AxiosInstance = axios, ): CancelablePromise => { return new CancelablePromise(async (resolve, reject, onCancel) => { @@ -357,12 +363,17 @@ export const request = ( options.responseHeader, ) + let transformedBody = responseBody + if (options.responseTransformer && isSuccess(response.status)) { + transformedBody = await options.responseTransformer(responseBody) + } + const result: ApiResult = { url, ok: isSuccess(response.status), status: response.status, statusText: response.statusText, - body: responseHeader ?? responseBody, + body: responseHeader ?? transformedBody, } catchErrorCodes(options, result) diff --git a/frontend/src/client/core/types.ts b/frontend/src/client/core/types.ts deleted file mode 100644 index 199c08d3df..0000000000 --- a/frontend/src/client/core/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ApiResult } from "./ApiResult" - -export type TResult = "body" | "raw" - -export type TApiResponse = Exclude< - T, - "raw" -> extends never - ? ApiResult - : ApiResult["body"] - -export type TConfig = { - _result?: T -} diff --git a/frontend/src/client/index.ts b/frontend/src/client/index.ts index adf1d0cabf..2228dde8b4 100644 --- a/frontend/src/client/index.ts +++ b/frontend/src/client/index.ts @@ -1,8 +1,6 @@ +// This file is auto-generated by @hey-api/openapi-ts export { ApiError } from "./core/ApiError" export { CancelablePromise, CancelError } from "./core/CancelablePromise" -export { OpenAPI } from "./core/OpenAPI" -export type { OpenAPIConfig } from "./core/OpenAPI" - -export * from "./models" -export * from "./schemas" -export * from "./services" +export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI" +export * from "./sdk.gen" +export * from "./types.gen" diff --git a/frontend/src/client/models.ts b/frontend/src/client/models.ts deleted file mode 100644 index 2c8074ddd6..0000000000 --- a/frontend/src/client/models.ts +++ /dev/null @@ -1,99 +0,0 @@ -export type Body_login_login_access_token = { - grant_type?: string | null - username: string - password: string - scope?: string - client_id?: string | null - client_secret?: string | null -} - -export type HTTPValidationError = { - detail?: Array -} - -export type ItemCreate = { - title: string - description?: string | null -} - -export type ItemPublic = { - title: string - description?: string | null - id: string - owner_id: string -} - -export type ItemUpdate = { - title?: string | null - description?: string | null -} - -export type ItemsPublic = { - data: Array - count: number -} - -export type Message = { - message: string -} - -export type NewPassword = { - token: string - new_password: string -} - -export type Token = { - access_token: string - token_type?: string -} - -export type UpdatePassword = { - current_password: string - new_password: string -} - -export type UserCreate = { - email: string - is_active?: boolean - is_superuser?: boolean - full_name?: string | null - password: string -} - -export type UserPublic = { - email: string - is_active?: boolean - is_superuser?: boolean - full_name?: string | null - id: string -} - -export type UserRegister = { - email: string - password: string - full_name?: string | null -} - -export type UserUpdate = { - email?: string | null - is_active?: boolean - is_superuser?: boolean - full_name?: string | null - password?: string | null -} - -export type UserUpdateMe = { - full_name?: string | null - email?: string | null -} - -export type UsersPublic = { - data: Array - count: number -} - -export type ValidationError = { - loc: Array - msg: string - type: string -} diff --git a/frontend/src/client/schemas.ts b/frontend/src/client/schemas.gen.ts similarity index 58% rename from frontend/src/client/schemas.ts rename to frontend/src/client/schemas.gen.ts index 9e92efd106..ca22051056 100644 --- a/frontend/src/client/schemas.ts +++ b/frontend/src/client/schemas.gen.ts @@ -1,8 +1,9 @@ -export const $Body_login_login_access_token = { +// This file is auto-generated by @hey-api/openapi-ts + +export const Body_login_login_access_tokenSchema = { properties: { grant_type: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", pattern: "password", @@ -11,22 +12,23 @@ export const $Body_login_login_access_token = { type: "null", }, ], + title: "Grant Type", }, username: { type: "string", - isRequired: true, + title: "Username", }, password: { type: "string", - isRequired: true, + title: "Password", }, scope: { type: "string", + title: "Scope", default: "", }, client_id: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", }, @@ -34,10 +36,10 @@ export const $Body_login_login_access_token = { type: "null", }, ], + title: "Client Id", }, client_secret: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", }, @@ -45,32 +47,38 @@ export const $Body_login_login_access_token = { type: "null", }, ], + title: "Client Secret", }, }, + type: "object", + required: ["username", "password"], + title: "Body_login-login_access_token", } as const -export const $HTTPValidationError = { +export const HTTPValidationErrorSchema = { properties: { detail: { - type: "array", - contains: { - type: "ValidationError", + items: { + $ref: "#/components/schemas/ValidationError", }, + type: "array", + title: "Detail", }, }, + type: "object", + title: "HTTPValidationError", } as const -export const $ItemCreate = { +export const ItemCreateSchema = { properties: { title: { type: "string", - isRequired: true, maxLength: 255, minLength: 1, + title: "Title", }, description: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -79,21 +87,24 @@ export const $ItemCreate = { type: "null", }, ], + title: "Description", }, }, + type: "object", + required: ["title"], + title: "ItemCreate", } as const -export const $ItemPublic = { +export const ItemPublicSchema = { properties: { title: { type: "string", - isRequired: true, maxLength: 255, minLength: 1, + title: "Title", }, description: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -102,25 +113,28 @@ export const $ItemPublic = { type: "null", }, ], + title: "Description", }, id: { type: "string", - isRequired: true, format: "uuid", + title: "Id", }, owner_id: { type: "string", - isRequired: true, format: "uuid", + title: "Owner Id", }, }, + type: "object", + required: ["title", "id", "owner_id"], + title: "ItemPublic", } as const -export const $ItemUpdate = { +export const ItemUpdateSchema = { properties: { title: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -130,10 +144,10 @@ export const $ItemUpdate = { type: "null", }, ], + title: "Title", }, description: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -142,99 +156,119 @@ export const $ItemUpdate = { type: "null", }, ], + title: "Description", }, }, + type: "object", + title: "ItemUpdate", } as const -export const $ItemsPublic = { +export const ItemsPublicSchema = { properties: { data: { - type: "array", - contains: { - type: "ItemPublic", + items: { + $ref: "#/components/schemas/ItemPublic", }, - isRequired: true, + type: "array", + title: "Data", }, count: { - type: "number", - isRequired: true, + type: "integer", + title: "Count", }, }, + type: "object", + required: ["data", "count"], + title: "ItemsPublic", } as const -export const $Message = { +export const MessageSchema = { properties: { message: { type: "string", - isRequired: true, + title: "Message", }, }, + type: "object", + required: ["message"], + title: "Message", } as const -export const $NewPassword = { +export const NewPasswordSchema = { properties: { token: { type: "string", - isRequired: true, + title: "Token", }, new_password: { type: "string", - isRequired: true, maxLength: 40, minLength: 8, + title: "New Password", }, }, + type: "object", + required: ["token", "new_password"], + title: "NewPassword", } as const -export const $Token = { +export const TokenSchema = { properties: { access_token: { type: "string", - isRequired: true, + title: "Access Token", }, token_type: { type: "string", + title: "Token Type", default: "bearer", }, }, + type: "object", + required: ["access_token"], + title: "Token", } as const -export const $UpdatePassword = { +export const UpdatePasswordSchema = { properties: { current_password: { type: "string", - isRequired: true, maxLength: 40, minLength: 8, + title: "Current Password", }, new_password: { type: "string", - isRequired: true, maxLength: 40, minLength: 8, + title: "New Password", }, }, + type: "object", + required: ["current_password", "new_password"], + title: "UpdatePassword", } as const -export const $UserCreate = { +export const UserCreateSchema = { properties: { email: { type: "string", - isRequired: true, - format: "email", maxLength: 255, + format: "email", + title: "Email", }, is_active: { type: "boolean", + title: "Is Active", default: true, }, is_superuser: { type: "boolean", + title: "Is Superuser", default: false, }, full_name: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -243,35 +277,40 @@ export const $UserCreate = { type: "null", }, ], + title: "Full Name", }, password: { type: "string", - isRequired: true, maxLength: 40, minLength: 8, + title: "Password", }, }, + type: "object", + required: ["email", "password"], + title: "UserCreate", } as const -export const $UserPublic = { +export const UserPublicSchema = { properties: { email: { type: "string", - isRequired: true, - format: "email", maxLength: 255, + format: "email", + title: "Email", }, is_active: { type: "boolean", + title: "Is Active", default: true, }, is_superuser: { type: "boolean", + title: "Is Superuser", default: false, }, full_name: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -280,32 +319,35 @@ export const $UserPublic = { type: "null", }, ], + title: "Full Name", }, id: { type: "string", - isRequired: true, format: "uuid", + title: "Id", }, }, + type: "object", + required: ["email", "id"], + title: "UserPublic", } as const -export const $UserRegister = { +export const UserRegisterSchema = { properties: { email: { type: "string", - isRequired: true, - format: "email", maxLength: 255, + format: "email", + title: "Email", }, password: { type: "string", - isRequired: true, maxLength: 40, minLength: 8, + title: "Password", }, full_name: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -314,36 +356,41 @@ export const $UserRegister = { type: "null", }, ], + title: "Full Name", }, }, + type: "object", + required: ["email", "password"], + title: "UserRegister", } as const -export const $UserUpdate = { +export const UserUpdateSchema = { properties: { email: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", - format: "email", maxLength: 255, + format: "email", }, { type: "null", }, ], + title: "Email", }, is_active: { type: "boolean", + title: "Is Active", default: true, }, is_superuser: { type: "boolean", + title: "Is Superuser", default: false, }, full_name: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -352,10 +399,10 @@ export const $UserUpdate = { type: "null", }, ], + title: "Full Name", }, password: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 40, @@ -365,15 +412,17 @@ export const $UserUpdate = { type: "null", }, ], + title: "Password", }, }, + type: "object", + title: "UserUpdate", } as const -export const $UserUpdateMe = { +export const UserUpdateMeSchema = { properties: { full_name: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", maxLength: 255, @@ -382,63 +431,71 @@ export const $UserUpdateMe = { type: "null", }, ], + title: "Full Name", }, email: { - type: "any-of", - contains: [ + anyOf: [ { type: "string", - format: "email", maxLength: 255, + format: "email", }, { type: "null", }, ], + title: "Email", }, }, + type: "object", + title: "UserUpdateMe", } as const -export const $UsersPublic = { +export const UsersPublicSchema = { properties: { data: { - type: "array", - contains: { - type: "UserPublic", + items: { + $ref: "#/components/schemas/UserPublic", }, - isRequired: true, + type: "array", + title: "Data", }, count: { - type: "number", - isRequired: true, + type: "integer", + title: "Count", }, }, + type: "object", + required: ["data", "count"], + title: "UsersPublic", } as const -export const $ValidationError = { +export const ValidationErrorSchema = { properties: { loc: { - type: "array", - contains: { - type: "any-of", - contains: [ + items: { + anyOf: [ { type: "string", }, { - type: "number", + type: "integer", }, ], }, - isRequired: true, + type: "array", + title: "Location", }, msg: { type: "string", - isRequired: true, + title: "Message", }, type: { type: "string", - isRequired: true, + title: "Error Type", }, }, + type: "object", + required: ["loc", "msg", "type"], + title: "ValidationError", } as const diff --git a/frontend/src/client/services.ts b/frontend/src/client/sdk.gen.ts similarity index 56% rename from frontend/src/client/services.ts rename to frontend/src/client/sdk.gen.ts index a7d58e9c02..4a6723ea70 100644 --- a/frontend/src/client/services.ts +++ b/frontend/src/client/sdk.gen.ts @@ -1,56 +1,191 @@ +// This file is auto-generated by @hey-api/openapi-ts + import type { CancelablePromise } from "./core/CancelablePromise" import { OpenAPI } from "./core/OpenAPI" import { request as __request } from "./core/request" - import type { - Body_login_login_access_token, - Message, - NewPassword, - Token, - UserPublic, - UpdatePassword, - UserCreate, - UserRegister, - UsersPublic, - UserUpdate, - UserUpdateMe, - ItemCreate, - ItemPublic, - ItemsPublic, - ItemUpdate, -} from "./models" + ReadItemsData, + ReadItemsResponse, + CreateItemData, + CreateItemResponse, + ReadItemData, + ReadItemResponse, + UpdateItemData, + UpdateItemResponse, + DeleteItemData, + DeleteItemResponse, + LoginAccessTokenData, + LoginAccessTokenResponse, + TestTokenResponse, + RecoverPasswordData, + RecoverPasswordResponse, + ResetPasswordData, + ResetPasswordResponse, + RecoverPasswordHtmlContentData, + RecoverPasswordHtmlContentResponse, + ReadUsersData, + ReadUsersResponse, + CreateUserData, + CreateUserResponse, + ReadUserMeResponse, + DeleteUserMeResponse, + UpdateUserMeData, + UpdateUserMeResponse, + UpdatePasswordMeData, + UpdatePasswordMeResponse, + RegisterUserData, + RegisterUserResponse, + ReadUserByIdData, + ReadUserByIdResponse, + UpdateUserData, + UpdateUserResponse, + DeleteUserData, + DeleteUserResponse, + TestEmailData, + TestEmailResponse, + HealthCheckResponse, +} from "./types.gen" -export type TDataLoginAccessToken = { - formData: Body_login_login_access_token -} -export type TDataRecoverPassword = { - email: string -} -export type TDataResetPassword = { - requestBody: NewPassword -} -export type TDataRecoverPasswordHtmlContent = { - email: string +export class ItemsService { + /** + * Read Items + * Retrieve items. + * @param data The data for the request. + * @param data.skip + * @param data.limit + * @returns ItemsPublic Successful Response + * @throws ApiError + */ + public static readItems( + data: ReadItemsData = {}, + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/items/", + query: { + skip: data.skip, + limit: data.limit, + }, + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Create Item + * Create new item. + * @param data The data for the request. + * @param data.requestBody + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static createItem( + data: CreateItemData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/items/", + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Read Item + * Get item by ID. + * @param data The data for the request. + * @param data.id + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static readItem( + data: ReadItemData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/items/{id}", + path: { + id: data.id, + }, + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Update Item + * Update an item. + * @param data The data for the request. + * @param data.id + * @param data.requestBody + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static updateItem( + data: UpdateItemData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "PUT", + url: "/api/v1/items/{id}", + path: { + id: data.id, + }, + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Delete Item + * Delete an item. + * @param data The data for the request. + * @param data.id + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteItem( + data: DeleteItemData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/api/v1/items/{id}", + path: { + id: data.id, + }, + errors: { + 422: "Validation Error", + }, + }) + } } export class LoginService { /** * Login Access Token * OAuth2 compatible token login, get an access token for future requests + * @param data The data for the request. + * @param data.formData * @returns Token Successful Response * @throws ApiError */ public static loginAccessToken( - data: TDataLoginAccessToken, - ): CancelablePromise { - const { formData } = data + data: LoginAccessTokenData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/login/access-token", - formData: formData, + formData: data.formData, mediaType: "application/x-www-form-urlencoded", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -61,7 +196,7 @@ export class LoginService { * @returns UserPublic Successful Response * @throws ApiError */ - public static testToken(): CancelablePromise { + public static testToken(): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/login/test-token", @@ -71,21 +206,22 @@ export class LoginService { /** * Recover Password * Password Recovery + * @param data The data for the request. + * @param data.email * @returns Message Successful Response * @throws ApiError */ public static recoverPassword( - data: TDataRecoverPassword, - ): CancelablePromise { - const { email } = data + data: RecoverPasswordData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/password-recovery/{email}", path: { - email, + email: data.email, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -93,20 +229,21 @@ export class LoginService { /** * Reset Password * Reset password + * @param data The data for the request. + * @param data.requestBody * @returns Message Successful Response * @throws ApiError */ public static resetPassword( - data: TDataResetPassword, - ): CancelablePromise { - const { requestBody } = data + data: ResetPasswordData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/reset-password/", - body: requestBody, + body: data.requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -114,73 +251,49 @@ export class LoginService { /** * Recover Password Html Content * HTML Content for Password Recovery + * @param data The data for the request. + * @param data.email * @returns string Successful Response * @throws ApiError */ public static recoverPasswordHtmlContent( - data: TDataRecoverPasswordHtmlContent, - ): CancelablePromise { - const { email } = data + data: RecoverPasswordHtmlContentData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/password-recovery-html-content/{email}", path: { - email, + email: data.email, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } } -export type TDataReadUsers = { - limit?: number - skip?: number -} -export type TDataCreateUser = { - requestBody: UserCreate -} -export type TDataUpdateUserMe = { - requestBody: UserUpdateMe -} -export type TDataUpdatePasswordMe = { - requestBody: UpdatePassword -} -export type TDataRegisterUser = { - requestBody: UserRegister -} -export type TDataReadUserById = { - userId: string -} -export type TDataUpdateUser = { - requestBody: UserUpdate - userId: string -} -export type TDataDeleteUser = { - userId: string -} - export class UsersService { /** * Read Users * Retrieve users. + * @param data The data for the request. + * @param data.skip + * @param data.limit * @returns UsersPublic Successful Response * @throws ApiError */ public static readUsers( - data: TDataReadUsers = {}, - ): CancelablePromise { - const { limit = 100, skip = 0 } = data + data: ReadUsersData = {}, + ): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/users/", query: { - skip, - limit, + skip: data.skip, + limit: data.limit, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -188,20 +301,21 @@ export class UsersService { /** * Create User * Create new user. + * @param data The data for the request. + * @param data.requestBody * @returns UserPublic Successful Response * @throws ApiError */ public static createUser( - data: TDataCreateUser, - ): CancelablePromise { - const { requestBody } = data + data: CreateUserData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/users/", - body: requestBody, + body: data.requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -212,7 +326,7 @@ export class UsersService { * @returns UserPublic Successful Response * @throws ApiError */ - public static readUserMe(): CancelablePromise { + public static readUserMe(): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/users/me", @@ -225,7 +339,7 @@ export class UsersService { * @returns Message Successful Response * @throws ApiError */ - public static deleteUserMe(): CancelablePromise { + public static deleteUserMe(): CancelablePromise { return __request(OpenAPI, { method: "DELETE", url: "/api/v1/users/me", @@ -235,20 +349,21 @@ export class UsersService { /** * Update User Me * Update own user. + * @param data The data for the request. + * @param data.requestBody * @returns UserPublic Successful Response * @throws ApiError */ public static updateUserMe( - data: TDataUpdateUserMe, - ): CancelablePromise { - const { requestBody } = data + data: UpdateUserMeData, + ): CancelablePromise { return __request(OpenAPI, { method: "PATCH", url: "/api/v1/users/me", - body: requestBody, + body: data.requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -256,20 +371,21 @@ export class UsersService { /** * Update Password Me * Update own password. + * @param data The data for the request. + * @param data.requestBody * @returns Message Successful Response * @throws ApiError */ public static updatePasswordMe( - data: TDataUpdatePasswordMe, - ): CancelablePromise { - const { requestBody } = data + data: UpdatePasswordMeData, + ): CancelablePromise { return __request(OpenAPI, { method: "PATCH", url: "/api/v1/users/me/password", - body: requestBody, + body: data.requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -277,20 +393,21 @@ export class UsersService { /** * Register User * Create new user without the need to be logged in. + * @param data The data for the request. + * @param data.requestBody * @returns UserPublic Successful Response * @throws ApiError */ public static registerUser( - data: TDataRegisterUser, - ): CancelablePromise { - const { requestBody } = data + data: RegisterUserData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/users/signup", - body: requestBody, + body: data.requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -298,21 +415,22 @@ export class UsersService { /** * Read User By Id * Get a specific user by id. + * @param data The data for the request. + * @param data.userId * @returns UserPublic Successful Response * @throws ApiError */ public static readUserById( - data: TDataReadUserById, - ): CancelablePromise { - const { userId } = data + data: ReadUserByIdData, + ): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/users/{user_id}", path: { - user_id: userId, + user_id: data.userId, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -320,23 +438,25 @@ export class UsersService { /** * Update User * Update a user. + * @param data The data for the request. + * @param data.userId + * @param data.requestBody * @returns UserPublic Successful Response * @throws ApiError */ public static updateUser( - data: TDataUpdateUser, - ): CancelablePromise { - const { requestBody, userId } = data + data: UpdateUserData, + ): CancelablePromise { return __request(OpenAPI, { method: "PATCH", url: "/api/v1/users/{user_id}", path: { - user_id: userId, + user_id: data.userId, }, - body: requestBody, + body: data.requestBody, mediaType: "application/json", errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -344,45 +464,47 @@ export class UsersService { /** * Delete User * Delete a user. + * @param data The data for the request. + * @param data.userId * @returns Message Successful Response * @throws ApiError */ - public static deleteUser(data: TDataDeleteUser): CancelablePromise { - const { userId } = data + public static deleteUser( + data: DeleteUserData, + ): CancelablePromise { return __request(OpenAPI, { method: "DELETE", url: "/api/v1/users/{user_id}", path: { - user_id: userId, + user_id: data.userId, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } } -export type TDataTestEmail = { - emailTo: string -} - export class UtilsService { /** * Test Email * Test emails. + * @param data The data for the request. + * @param data.emailTo * @returns Message Successful Response * @throws ApiError */ - public static testEmail(data: TDataTestEmail): CancelablePromise { - const { emailTo } = data + public static testEmail( + data: TestEmailData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/utils/test-email/", query: { - email_to: emailTo, + email_to: data.emailTo, }, errors: { - 422: `Validation Error`, + 422: "Validation Error", }, }) } @@ -392,138 +514,10 @@ export class UtilsService { * @returns boolean Successful Response * @throws ApiError */ - public static healthCheck(): CancelablePromise { + public static healthCheck(): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/utils/health-check/", }) } } - -export type TDataReadItems = { - limit?: number - skip?: number -} -export type TDataCreateItem = { - requestBody: ItemCreate -} -export type TDataReadItem = { - id: string -} -export type TDataUpdateItem = { - id: string - requestBody: ItemUpdate -} -export type TDataDeleteItem = { - id: string -} - -export class ItemsService { - /** - * Read Items - * Retrieve items. - * @returns ItemsPublic Successful Response - * @throws ApiError - */ - public static readItems( - data: TDataReadItems = {}, - ): CancelablePromise { - const { limit = 100, skip = 0 } = data - return __request(OpenAPI, { - method: "GET", - url: "/api/v1/items/", - query: { - skip, - limit, - }, - errors: { - 422: `Validation Error`, - }, - }) - } - - /** - * Create Item - * Create new item. - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static createItem( - data: TDataCreateItem, - ): CancelablePromise { - const { requestBody } = data - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/items/", - body: requestBody, - mediaType: "application/json", - errors: { - 422: `Validation Error`, - }, - }) - } - - /** - * Read Item - * Get item by ID. - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static readItem(data: TDataReadItem): CancelablePromise { - const { id } = data - return __request(OpenAPI, { - method: "GET", - url: "/api/v1/items/{id}", - path: { - id, - }, - errors: { - 422: `Validation Error`, - }, - }) - } - - /** - * Update Item - * Update an item. - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static updateItem( - data: TDataUpdateItem, - ): CancelablePromise { - const { id, requestBody } = data - return __request(OpenAPI, { - method: "PUT", - url: "/api/v1/items/{id}", - path: { - id, - }, - body: requestBody, - mediaType: "application/json", - errors: { - 422: `Validation Error`, - }, - }) - } - - /** - * Delete Item - * Delete an item. - * @returns Message Successful Response - * @throws ApiError - */ - public static deleteItem(data: TDataDeleteItem): CancelablePromise { - const { id } = data - return __request(OpenAPI, { - method: "DELETE", - url: "/api/v1/items/{id}", - path: { - id, - }, - errors: { - 422: `Validation Error`, - }, - }) - } -} diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts new file mode 100644 index 0000000000..54f5ee5730 --- /dev/null +++ b/frontend/src/client/types.gen.ts @@ -0,0 +1,221 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Body_login_login_access_token = { + grant_type?: string | null + username: string + password: string + scope?: string + client_id?: string | null + client_secret?: string | null +} + +export type HTTPValidationError = { + detail?: Array +} + +export type ItemCreate = { + title: string + description?: string | null +} + +export type ItemPublic = { + title: string + description?: string | null + id: string + owner_id: string +} + +export type ItemsPublic = { + data: Array + count: number +} + +export type ItemUpdate = { + title?: string | null + description?: string | null +} + +export type Message = { + message: string +} + +export type NewPassword = { + token: string + new_password: string +} + +export type Token = { + access_token: string + token_type?: string +} + +export type UpdatePassword = { + current_password: string + new_password: string +} + +export type UserCreate = { + email: string + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + password: string +} + +export type UserPublic = { + email: string + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + id: string +} + +export type UserRegister = { + email: string + password: string + full_name?: string | null +} + +export type UsersPublic = { + data: Array + count: number +} + +export type UserUpdate = { + email?: string | null + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + password?: string | null +} + +export type UserUpdateMe = { + full_name?: string | null + email?: string | null +} + +export type ValidationError = { + loc: Array + msg: string + type: string +} + +export type ReadItemsData = { + limit?: number + skip?: number +} + +export type ReadItemsResponse = ItemsPublic + +export type CreateItemData = { + requestBody: ItemCreate +} + +export type CreateItemResponse = ItemPublic + +export type ReadItemData = { + id: string +} + +export type ReadItemResponse = ItemPublic + +export type UpdateItemData = { + id: string + requestBody: ItemUpdate +} + +export type UpdateItemResponse = ItemPublic + +export type DeleteItemData = { + id: string +} + +export type DeleteItemResponse = Message + +export type LoginAccessTokenData = { + formData: Body_login_login_access_token +} + +export type LoginAccessTokenResponse = Token + +export type TestTokenResponse = UserPublic + +export type RecoverPasswordData = { + email: string +} + +export type RecoverPasswordResponse = Message + +export type ResetPasswordData = { + requestBody: NewPassword +} + +export type ResetPasswordResponse = Message + +export type RecoverPasswordHtmlContentData = { + email: string +} + +export type RecoverPasswordHtmlContentResponse = string + +export type ReadUsersData = { + limit?: number + skip?: number +} + +export type ReadUsersResponse = UsersPublic + +export type CreateUserData = { + requestBody: UserCreate +} + +export type CreateUserResponse = UserPublic + +export type ReadUserMeResponse = UserPublic + +export type DeleteUserMeResponse = Message + +export type UpdateUserMeData = { + requestBody: UserUpdateMe +} + +export type UpdateUserMeResponse = UserPublic + +export type UpdatePasswordMeData = { + requestBody: UpdatePassword +} + +export type UpdatePasswordMeResponse = Message + +export type RegisterUserData = { + requestBody: UserRegister +} + +export type RegisterUserResponse = UserPublic + +export type ReadUserByIdData = { + userId: string +} + +export type ReadUserByIdResponse = UserPublic + +export type UpdateUserData = { + requestBody: UserUpdate + userId: string +} + +export type UpdateUserResponse = UserPublic + +export type DeleteUserData = { + userId: string +} + +export type DeleteUserResponse = Message + +export type TestEmailData = { + emailTo: string +} + +export type TestEmailResponse = Message + +export type HealthCheckResponse = boolean From bc85640ddb1bda2fb8e46542d395fac321ee60a1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 11:07:21 +0000 Subject: [PATCH 726/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 384620e35c..be5bda2af8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Features + +* ✨ Migrate to latest openapi-ts. PR [#1430](https://github.com/fastapi/full-stack-fastapi-template/pull/1430) by [@patrick91](https://github.com/patrick91). + ### Refactors * 👷 Improve Playwright CI speed: sharding (paralel runs), run in Docker to use cache, use env vars. PR [#1405](https://github.com/fastapi/full-stack-fastapi-template/pull/1405) by [@tiangolo](https://github.com/tiangolo). From ace6839a2d1d2106ab510d87c13e24f8a9e67a92 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Wed, 27 Nov 2024 12:42:44 +0100 Subject: [PATCH 727/771] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20modify=20?= =?UTF-8?q?id=20script=20in=20favor=20of=20openapi-ts=20config=20(#1434)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/modify-openapi-operationids.js | 36 ------ frontend/openapi-ts.config.ts | 13 ++ frontend/src/client/sdk.gen.ts | 160 ++++++++++++------------ frontend/src/client/types.gen.ts | 80 ++++++------ scripts/generate-client.sh | 1 - 5 files changed, 133 insertions(+), 157 deletions(-) delete mode 100644 frontend/modify-openapi-operationids.js diff --git a/frontend/modify-openapi-operationids.js b/frontend/modify-openapi-operationids.js deleted file mode 100644 index b22fd17f9e..0000000000 --- a/frontend/modify-openapi-operationids.js +++ /dev/null @@ -1,36 +0,0 @@ -import * as fs from "node:fs" - -async function modifyOpenAPIFile(filePath) { - try { - const data = await fs.promises.readFile(filePath) - const openapiContent = JSON.parse(data) - - const paths = openapiContent.paths - for (const pathKey of Object.keys(paths)) { - const pathData = paths[pathKey] - for (const method of Object.keys(pathData)) { - const operation = pathData[method] - if (operation.tags && operation.tags.length > 0) { - const tag = operation.tags[0] - const operationId = operation.operationId - const toRemove = `${tag}-` - if (operationId.startsWith(toRemove)) { - const newOperationId = operationId.substring(toRemove.length) - operation.operationId = newOperationId - } - } - } - } - - await fs.promises.writeFile( - filePath, - JSON.stringify(openapiContent, null, 2), - ) - console.log("File successfully modified") - } catch (err) { - console.error("Error:", err) - } -} - -const filePath = "./openapi.json" -modifyOpenAPIFile(filePath) diff --git a/frontend/openapi-ts.config.ts b/frontend/openapi-ts.config.ts index 178d04b3bf..3cd5ff3c46 100644 --- a/frontend/openapi-ts.config.ts +++ b/frontend/openapi-ts.config.ts @@ -10,6 +10,19 @@ export default defineConfig({ name: "@hey-api/sdk", // NOTE: this doesn't allow tree-shaking asClass: true, + operationId: true, + methodNameBuilder: (operation) => { + // @ts-ignore + let name: string = operation.name + // @ts-ignore + let service: string = operation.service + + if (service && name.toLowerCase().startsWith(service.toLowerCase())) { + name = name.slice(service.length) + } + + return name.charAt(0).toLowerCase() + name.slice(1) + }, }, ], }) diff --git a/frontend/src/client/sdk.gen.ts b/frontend/src/client/sdk.gen.ts index 4a6723ea70..92ded2bde8 100644 --- a/frontend/src/client/sdk.gen.ts +++ b/frontend/src/client/sdk.gen.ts @@ -4,46 +4,46 @@ import type { CancelablePromise } from "./core/CancelablePromise" import { OpenAPI } from "./core/OpenAPI" import { request as __request } from "./core/request" import type { - ReadItemsData, - ReadItemsResponse, - CreateItemData, - CreateItemResponse, - ReadItemData, - ReadItemResponse, - UpdateItemData, - UpdateItemResponse, - DeleteItemData, - DeleteItemResponse, - LoginAccessTokenData, - LoginAccessTokenResponse, - TestTokenResponse, - RecoverPasswordData, - RecoverPasswordResponse, - ResetPasswordData, - ResetPasswordResponse, - RecoverPasswordHtmlContentData, - RecoverPasswordHtmlContentResponse, - ReadUsersData, - ReadUsersResponse, - CreateUserData, - CreateUserResponse, - ReadUserMeResponse, - DeleteUserMeResponse, - UpdateUserMeData, - UpdateUserMeResponse, - UpdatePasswordMeData, - UpdatePasswordMeResponse, - RegisterUserData, - RegisterUserResponse, - ReadUserByIdData, - ReadUserByIdResponse, - UpdateUserData, - UpdateUserResponse, - DeleteUserData, - DeleteUserResponse, - TestEmailData, - TestEmailResponse, - HealthCheckResponse, + ItemsReadItemsData, + ItemsReadItemsResponse, + ItemsCreateItemData, + ItemsCreateItemResponse, + ItemsReadItemData, + ItemsReadItemResponse, + ItemsUpdateItemData, + ItemsUpdateItemResponse, + ItemsDeleteItemData, + ItemsDeleteItemResponse, + LoginLoginAccessTokenData, + LoginLoginAccessTokenResponse, + LoginTestTokenResponse, + LoginRecoverPasswordData, + LoginRecoverPasswordResponse, + LoginResetPasswordData, + LoginResetPasswordResponse, + LoginRecoverPasswordHtmlContentData, + LoginRecoverPasswordHtmlContentResponse, + UsersReadUsersData, + UsersReadUsersResponse, + UsersCreateUserData, + UsersCreateUserResponse, + UsersReadUserMeResponse, + UsersDeleteUserMeResponse, + UsersUpdateUserMeData, + UsersUpdateUserMeResponse, + UsersUpdatePasswordMeData, + UsersUpdatePasswordMeResponse, + UsersRegisterUserData, + UsersRegisterUserResponse, + UsersReadUserByIdData, + UsersReadUserByIdResponse, + UsersUpdateUserData, + UsersUpdateUserResponse, + UsersDeleteUserData, + UsersDeleteUserResponse, + UtilsTestEmailData, + UtilsTestEmailResponse, + UtilsHealthCheckResponse, } from "./types.gen" export class ItemsService { @@ -57,8 +57,8 @@ export class ItemsService { * @throws ApiError */ public static readItems( - data: ReadItemsData = {}, - ): CancelablePromise { + data: ItemsReadItemsData = {}, + ): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/items/", @@ -81,8 +81,8 @@ export class ItemsService { * @throws ApiError */ public static createItem( - data: CreateItemData, - ): CancelablePromise { + data: ItemsCreateItemData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/items/", @@ -103,8 +103,8 @@ export class ItemsService { * @throws ApiError */ public static readItem( - data: ReadItemData, - ): CancelablePromise { + data: ItemsReadItemData, + ): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/items/{id}", @@ -127,8 +127,8 @@ export class ItemsService { * @throws ApiError */ public static updateItem( - data: UpdateItemData, - ): CancelablePromise { + data: ItemsUpdateItemData, + ): CancelablePromise { return __request(OpenAPI, { method: "PUT", url: "/api/v1/items/{id}", @@ -152,8 +152,8 @@ export class ItemsService { * @throws ApiError */ public static deleteItem( - data: DeleteItemData, - ): CancelablePromise { + data: ItemsDeleteItemData, + ): CancelablePromise { return __request(OpenAPI, { method: "DELETE", url: "/api/v1/items/{id}", @@ -177,8 +177,8 @@ export class LoginService { * @throws ApiError */ public static loginAccessToken( - data: LoginAccessTokenData, - ): CancelablePromise { + data: LoginLoginAccessTokenData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/login/access-token", @@ -196,7 +196,7 @@ export class LoginService { * @returns UserPublic Successful Response * @throws ApiError */ - public static testToken(): CancelablePromise { + public static testToken(): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/login/test-token", @@ -212,8 +212,8 @@ export class LoginService { * @throws ApiError */ public static recoverPassword( - data: RecoverPasswordData, - ): CancelablePromise { + data: LoginRecoverPasswordData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/password-recovery/{email}", @@ -235,8 +235,8 @@ export class LoginService { * @throws ApiError */ public static resetPassword( - data: ResetPasswordData, - ): CancelablePromise { + data: LoginResetPasswordData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/reset-password/", @@ -257,8 +257,8 @@ export class LoginService { * @throws ApiError */ public static recoverPasswordHtmlContent( - data: RecoverPasswordHtmlContentData, - ): CancelablePromise { + data: LoginRecoverPasswordHtmlContentData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/password-recovery-html-content/{email}", @@ -283,8 +283,8 @@ export class UsersService { * @throws ApiError */ public static readUsers( - data: ReadUsersData = {}, - ): CancelablePromise { + data: UsersReadUsersData = {}, + ): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/users/", @@ -307,8 +307,8 @@ export class UsersService { * @throws ApiError */ public static createUser( - data: CreateUserData, - ): CancelablePromise { + data: UsersCreateUserData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/users/", @@ -326,7 +326,7 @@ export class UsersService { * @returns UserPublic Successful Response * @throws ApiError */ - public static readUserMe(): CancelablePromise { + public static readUserMe(): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/users/me", @@ -339,7 +339,7 @@ export class UsersService { * @returns Message Successful Response * @throws ApiError */ - public static deleteUserMe(): CancelablePromise { + public static deleteUserMe(): CancelablePromise { return __request(OpenAPI, { method: "DELETE", url: "/api/v1/users/me", @@ -355,8 +355,8 @@ export class UsersService { * @throws ApiError */ public static updateUserMe( - data: UpdateUserMeData, - ): CancelablePromise { + data: UsersUpdateUserMeData, + ): CancelablePromise { return __request(OpenAPI, { method: "PATCH", url: "/api/v1/users/me", @@ -377,8 +377,8 @@ export class UsersService { * @throws ApiError */ public static updatePasswordMe( - data: UpdatePasswordMeData, - ): CancelablePromise { + data: UsersUpdatePasswordMeData, + ): CancelablePromise { return __request(OpenAPI, { method: "PATCH", url: "/api/v1/users/me/password", @@ -399,8 +399,8 @@ export class UsersService { * @throws ApiError */ public static registerUser( - data: RegisterUserData, - ): CancelablePromise { + data: UsersRegisterUserData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/users/signup", @@ -421,8 +421,8 @@ export class UsersService { * @throws ApiError */ public static readUserById( - data: ReadUserByIdData, - ): CancelablePromise { + data: UsersReadUserByIdData, + ): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/users/{user_id}", @@ -445,8 +445,8 @@ export class UsersService { * @throws ApiError */ public static updateUser( - data: UpdateUserData, - ): CancelablePromise { + data: UsersUpdateUserData, + ): CancelablePromise { return __request(OpenAPI, { method: "PATCH", url: "/api/v1/users/{user_id}", @@ -470,8 +470,8 @@ export class UsersService { * @throws ApiError */ public static deleteUser( - data: DeleteUserData, - ): CancelablePromise { + data: UsersDeleteUserData, + ): CancelablePromise { return __request(OpenAPI, { method: "DELETE", url: "/api/v1/users/{user_id}", @@ -495,8 +495,8 @@ export class UtilsService { * @throws ApiError */ public static testEmail( - data: TestEmailData, - ): CancelablePromise { + data: UtilsTestEmailData, + ): CancelablePromise { return __request(OpenAPI, { method: "POST", url: "/api/v1/utils/test-email/", @@ -514,7 +514,7 @@ export class UtilsService { * @returns boolean Successful Response * @throws ApiError */ - public static healthCheck(): CancelablePromise { + public static healthCheck(): CancelablePromise { return __request(OpenAPI, { method: "GET", url: "/api/v1/utils/health-check/", diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index 54f5ee5730..c2a58d06cb 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -100,122 +100,122 @@ export type ValidationError = { type: string } -export type ReadItemsData = { +export type ItemsReadItemsData = { limit?: number skip?: number } -export type ReadItemsResponse = ItemsPublic +export type ItemsReadItemsResponse = ItemsPublic -export type CreateItemData = { +export type ItemsCreateItemData = { requestBody: ItemCreate } -export type CreateItemResponse = ItemPublic +export type ItemsCreateItemResponse = ItemPublic -export type ReadItemData = { +export type ItemsReadItemData = { id: string } -export type ReadItemResponse = ItemPublic +export type ItemsReadItemResponse = ItemPublic -export type UpdateItemData = { +export type ItemsUpdateItemData = { id: string requestBody: ItemUpdate } -export type UpdateItemResponse = ItemPublic +export type ItemsUpdateItemResponse = ItemPublic -export type DeleteItemData = { +export type ItemsDeleteItemData = { id: string } -export type DeleteItemResponse = Message +export type ItemsDeleteItemResponse = Message -export type LoginAccessTokenData = { +export type LoginLoginAccessTokenData = { formData: Body_login_login_access_token } -export type LoginAccessTokenResponse = Token +export type LoginLoginAccessTokenResponse = Token -export type TestTokenResponse = UserPublic +export type LoginTestTokenResponse = UserPublic -export type RecoverPasswordData = { +export type LoginRecoverPasswordData = { email: string } -export type RecoverPasswordResponse = Message +export type LoginRecoverPasswordResponse = Message -export type ResetPasswordData = { +export type LoginResetPasswordData = { requestBody: NewPassword } -export type ResetPasswordResponse = Message +export type LoginResetPasswordResponse = Message -export type RecoverPasswordHtmlContentData = { +export type LoginRecoverPasswordHtmlContentData = { email: string } -export type RecoverPasswordHtmlContentResponse = string +export type LoginRecoverPasswordHtmlContentResponse = string -export type ReadUsersData = { +export type UsersReadUsersData = { limit?: number skip?: number } -export type ReadUsersResponse = UsersPublic +export type UsersReadUsersResponse = UsersPublic -export type CreateUserData = { +export type UsersCreateUserData = { requestBody: UserCreate } -export type CreateUserResponse = UserPublic +export type UsersCreateUserResponse = UserPublic -export type ReadUserMeResponse = UserPublic +export type UsersReadUserMeResponse = UserPublic -export type DeleteUserMeResponse = Message +export type UsersDeleteUserMeResponse = Message -export type UpdateUserMeData = { +export type UsersUpdateUserMeData = { requestBody: UserUpdateMe } -export type UpdateUserMeResponse = UserPublic +export type UsersUpdateUserMeResponse = UserPublic -export type UpdatePasswordMeData = { +export type UsersUpdatePasswordMeData = { requestBody: UpdatePassword } -export type UpdatePasswordMeResponse = Message +export type UsersUpdatePasswordMeResponse = Message -export type RegisterUserData = { +export type UsersRegisterUserData = { requestBody: UserRegister } -export type RegisterUserResponse = UserPublic +export type UsersRegisterUserResponse = UserPublic -export type ReadUserByIdData = { +export type UsersReadUserByIdData = { userId: string } -export type ReadUserByIdResponse = UserPublic +export type UsersReadUserByIdResponse = UserPublic -export type UpdateUserData = { +export type UsersUpdateUserData = { requestBody: UserUpdate userId: string } -export type UpdateUserResponse = UserPublic +export type UsersUpdateUserResponse = UserPublic -export type DeleteUserData = { +export type UsersDeleteUserData = { userId: string } -export type DeleteUserResponse = Message +export type UsersDeleteUserResponse = Message -export type TestEmailData = { +export type UtilsTestEmailData = { emailTo: string } -export type TestEmailResponse = Message +export type UtilsTestEmailResponse = Message -export type HealthCheckResponse = boolean +export type UtilsHealthCheckResponse = boolean diff --git a/scripts/generate-client.sh b/scripts/generate-client.sh index f7a564fdf0..1e76864d42 100644 --- a/scripts/generate-client.sh +++ b/scripts/generate-client.sh @@ -6,7 +6,6 @@ set -x cd backend python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()))" > ../openapi.json cd .. -node frontend/modify-openapi-operationids.js mv openapi.json frontend/ cd frontend npm run generate-client From 858d1f61419123dff337f23b7ab0c9d3d5721116 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 11:43:08 +0000 Subject: [PATCH 728/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index be5bda2af8..89797bd56c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ ### Refactors +* ♻️ Remove modify id script in favor of openapi-ts config. PR [#1434](https://github.com/fastapi/full-stack-fastapi-template/pull/1434) by [@patrick91](https://github.com/patrick91). * 👷 Improve Playwright CI speed: sharding (paralel runs), run in Docker to use cache, use env vars. PR [#1405](https://github.com/fastapi/full-stack-fastapi-template/pull/1405) by [@tiangolo](https://github.com/tiangolo). * ♻️ Add PaginationFooter component. PR [#1381](https://github.com/fastapi/full-stack-fastapi-template/pull/1381) by [@saltie2193](https://github.com/saltie2193). * ♻️ Refactored code to use encryption algorithm name from settings for consistency. PR [#1160](https://github.com/fastapi/full-stack-fastapi-template/pull/1160) by [@sameeramin](https://github.com/sameeramin). From 9974cfc543e105f34732dfd447fb20d8d070c902 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Mon, 2 Dec 2024 12:57:53 +0100 Subject: [PATCH 729/771] =?UTF-8?q?=E2=9C=A8=20Add=20private,=20local=20on?= =?UTF-8?q?ly,=20API=20for=20usage=20in=20E2E=20tests=20(#1429)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions --- .github/workflows/generate-client.yml | 4 ++ .github/workflows/playwright.yml | 12 ++++++ backend/app/api/main.py | 7 +++- backend/app/api/routes/private.py | 38 +++++++++++++++++ backend/app/tests/api/routes/test_private.py | 26 ++++++++++++ frontend/package.json | 2 +- frontend/tests/user-settings.spec.ts | 43 +++++++------------- frontend/tests/utils/privateApi.ts | 22 ++++++++++ frontend/tsconfig.build.json | 4 ++ 9 files changed, 127 insertions(+), 31 deletions(-) create mode 100644 backend/app/api/routes/private.py create mode 100644 backend/app/tests/api/routes/test_private.py create mode 100644 frontend/tests/utils/privateApi.ts create mode 100644 frontend/tsconfig.build.json diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index 1cf417ab89..b1787b0489 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -39,6 +39,10 @@ jobs: - run: uv run bash scripts/generate-client.sh env: VIRTUAL_ENV: backend/.venv + ENVIRONMENT: production + SECRET_KEY: just-for-generating-client + POSTGRES_PASSWORD: just-for-generating-client + FIRST_SUPERUSER_PASSWORD: just-for-generating-client - name: Add changes to git run: | git config --local user.email "github-actions@github.com" diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 8c741221f7..b78f32c623 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -59,6 +59,18 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "0.4.15" + enable-cache: true + - run: uv sync + working-directory: backend + - run: npm ci + working-directory: frontend + - run: uv run bash scripts/generate-client.sh + env: + VIRTUAL_ENV: backend/.venv - run: docker compose build - run: docker compose down -v --remove-orphans - name: Run Playwright tests diff --git a/backend/app/api/main.py b/backend/app/api/main.py index 09e0663fc3..45e930c4a1 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -1,9 +1,14 @@ from fastapi import APIRouter -from app.api.routes import items, login, users, utils +from app.api.routes import items, login, private, users, utils +from app.core.config import settings api_router = APIRouter() api_router.include_router(login.router, tags=["login"]) api_router.include_router(users.router, prefix="/users", tags=["users"]) api_router.include_router(utils.router, prefix="/utils", tags=["utils"]) api_router.include_router(items.router, prefix="/items", tags=["items"]) + + +if settings.ENVIRONMENT == "local": + api_router.include_router(private.router) diff --git a/backend/app/api/routes/private.py b/backend/app/api/routes/private.py new file mode 100644 index 0000000000..9f33ef1900 --- /dev/null +++ b/backend/app/api/routes/private.py @@ -0,0 +1,38 @@ +from typing import Any + +from fastapi import APIRouter +from pydantic import BaseModel + +from app.api.deps import SessionDep +from app.core.security import get_password_hash +from app.models import ( + User, + UserPublic, +) + +router = APIRouter(tags=["private"], prefix="/private") + + +class PrivateUserCreate(BaseModel): + email: str + password: str + full_name: str + is_verified: bool = False + + +@router.post("/users/", response_model=UserPublic) +def create_user(user_in: PrivateUserCreate, session: SessionDep) -> Any: + """ + Create a new user. + """ + + user = User( + email=user_in.email, + full_name=user_in.full_name, + hashed_password=get_password_hash(user_in.password), + ) + + session.add(user) + session.commit() + + return user diff --git a/backend/app/tests/api/routes/test_private.py b/backend/app/tests/api/routes/test_private.py new file mode 100644 index 0000000000..1e1f985021 --- /dev/null +++ b/backend/app/tests/api/routes/test_private.py @@ -0,0 +1,26 @@ +from fastapi.testclient import TestClient +from sqlmodel import Session, select + +from app.core.config import settings +from app.models import User + + +def test_create_user(client: TestClient, db: Session) -> None: + r = client.post( + f"{settings.API_V1_STR}/private/users/", + json={ + "email": "pollo@listo.com", + "password": "password123", + "full_name": "Pollo Listo", + }, + ) + + assert r.status_code == 200 + + data = r.json() + + user = db.exec(select(User).where(User.id == data["id"])).first() + + assert user + assert user.email == "pollo@listo.com" + assert user.full_name == "Pollo Listo" diff --git a/frontend/package.json b/frontend/package.json index 84f9841485..546028c9a9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "tsc -p tsconfig.build.json && vite build", "lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./", "preview": "vite preview", "generate-client": "openapi-ts" diff --git a/frontend/tests/user-settings.spec.ts b/frontend/tests/user-settings.spec.ts index a3a8a27490..8175d155c4 100644 --- a/frontend/tests/user-settings.spec.ts +++ b/frontend/tests/user-settings.spec.ts @@ -1,7 +1,8 @@ import { expect, test } from "@playwright/test" import { firstSuperuser, firstSuperuserPassword } from "./config.ts" import { randomEmail, randomPassword } from "./utils/random" -import { logInUser, logOutUser, signUpNewUser } from "./utils/user" +import { logInUser, logOutUser } from "./utils/user" +import { createUser } from "./utils/privateApi.ts" const tabs = ["My profile", "Password", "Appearance"] @@ -26,13 +27,11 @@ test.describe("Edit user full name and email successfully", () => { test.use({ storageState: { cookies: [], origins: [] } }) test("Edit user name with a valid name", async ({ page }) => { - const fullName = "Test User" const email = randomEmail() const updatedName = "Test User 2" const password = randomPassword() - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + await createUser({ email, password }) // Log in the user await logInUser(page, email, password) @@ -50,13 +49,11 @@ test.describe("Edit user full name and email successfully", () => { }) test("Edit user email with a valid email", async ({ page }) => { - const fullName = "Test User" const email = randomEmail() const updatedEmail = randomEmail() const password = randomPassword() - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + await createUser({ email, password }) // Log in the user await logInUser(page, email, password) @@ -77,13 +74,11 @@ test.describe("Edit user with invalid data", () => { test.use({ storageState: { cookies: [], origins: [] } }) test("Edit user email with an invalid email", async ({ page }) => { - const fullName = "Test User" const email = randomEmail() const password = randomPassword() const invalidEmail = "" - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + await createUser({ email, password }) // Log in the user await logInUser(page, email, password) @@ -97,13 +92,11 @@ test.describe("Edit user with invalid data", () => { }) test("Cancel edit action restores original name", async ({ page }) => { - const fullName = "Test User" const email = randomEmail() const password = randomPassword() const updatedName = "Test User" - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + const user = await createUser({ email, password }) // Log in the user await logInUser(page, email, password) @@ -114,18 +107,18 @@ test.describe("Edit user with invalid data", () => { await page.getByLabel("Full name").fill(updatedName) await page.getByRole("button", { name: "Cancel" }).first().click() await expect( - page.getByLabel("My profile").getByText(fullName, { exact: true }), + page + .getByLabel("My profile") + .getByText(user.full_name as string, { exact: true }), ).toBeVisible() }) test("Cancel edit action restores original email", async ({ page }) => { - const fullName = "Test User" const email = randomEmail() const password = randomPassword() const updatedEmail = randomEmail() - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + await createUser({ email, password }) // Log in the user await logInUser(page, email, password) @@ -147,13 +140,11 @@ test.describe("Change password successfully", () => { test.use({ storageState: { cookies: [], origins: [] } }) test("Update password successfully", async ({ page }) => { - const fullName = "Test User" const email = randomEmail() const password = randomPassword() const NewPassword = randomPassword() - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + await createUser({ email, password }) // Log in the user await logInUser(page, email, password) @@ -177,13 +168,11 @@ test.describe("Change password with invalid data", () => { test.use({ storageState: { cookies: [], origins: [] } }) test("Update password with weak passwords", async ({ page }) => { - const fullName = "Test User" const email = randomEmail() const password = randomPassword() const weakPassword = "weak" - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + await createUser({ email, password }) // Log in the user await logInUser(page, email, password) @@ -201,14 +190,12 @@ test.describe("Change password with invalid data", () => { test("New password and confirmation password do not match", async ({ page, }) => { - const fullName = "Test User" const email = randomEmail() const password = randomPassword() const newPassword = randomPassword() const confirmPassword = randomPassword() - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + await createUser({ email, password }) // Log in the user await logInUser(page, email, password) @@ -223,12 +210,10 @@ test.describe("Change password with invalid data", () => { }) test("Current password and new password are the same", async ({ page }) => { - const fullName = "Test User" const email = randomEmail() const password = randomPassword() - // Sign up a new user - await signUpNewUser(page, fullName, email, password) + await createUser({ email, password }) // Log in the user await logInUser(page, email, password) diff --git a/frontend/tests/utils/privateApi.ts b/frontend/tests/utils/privateApi.ts new file mode 100644 index 0000000000..b6fa0afe67 --- /dev/null +++ b/frontend/tests/utils/privateApi.ts @@ -0,0 +1,22 @@ +// Note: the `PrivateService` is only available when generating the client +// for local environments +import { OpenAPI, PrivateService } from "../../src/client" + +OpenAPI.BASE = `${process.env.VITE_API_URL}` + +export const createUser = async ({ + email, + password, +}: { + email: string + password: string +}) => { + return await PrivateService.createUser({ + requestBody: { + email, + password, + is_verified: true, + full_name: "Test User", + }, + }) +} diff --git a/frontend/tsconfig.build.json b/frontend/tsconfig.build.json new file mode 100644 index 0000000000..13bd2efc21 --- /dev/null +++ b/frontend/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["tests/**/*.ts"] +} From 1271dbd2afc7754e48cdc3f51936228e75d3ae12 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 2 Dec 2024 11:58:11 +0000 Subject: [PATCH 730/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 89797bd56c..470e3ee825 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Add private, local only, API for usage in E2E tests. PR [#1429](https://github.com/fastapi/full-stack-fastapi-template/pull/1429) by [@patrick91](https://github.com/patrick91). * ✨ Migrate to latest openapi-ts. PR [#1430](https://github.com/fastapi/full-stack-fastapi-template/pull/1430) by [@patrick91](https://github.com/patrick91). ### Refactors From 36d5c9f6b7d871afd36a16241a752988200acb72 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Mon, 2 Dec 2024 13:04:03 +0100 Subject: [PATCH 731/771] =?UTF-8?q?=F0=9F=8E=A8=20Move=20`prefix`=20and=20?= =?UTF-8?q?`tags`=20to=20routers=20(#1439)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/main.py | 8 ++++---- backend/app/api/routes/items.py | 2 +- backend/app/api/routes/login.py | 2 +- backend/app/api/routes/users.py | 2 +- backend/app/api/routes/utils.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/app/api/main.py b/backend/app/api/main.py index 45e930c4a1..eac18c8e8f 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -4,10 +4,10 @@ from app.core.config import settings api_router = APIRouter() -api_router.include_router(login.router, tags=["login"]) -api_router.include_router(users.router, prefix="/users", tags=["users"]) -api_router.include_router(utils.router, prefix="/utils", tags=["utils"]) -api_router.include_router(items.router, prefix="/items", tags=["items"]) +api_router.include_router(login.router) +api_router.include_router(users.router) +api_router.include_router(utils.router) +api_router.include_router(items.router) if settings.ENVIRONMENT == "local": diff --git a/backend/app/api/routes/items.py b/backend/app/api/routes/items.py index 67196c2366..177dc1e476 100644 --- a/backend/app/api/routes/items.py +++ b/backend/app/api/routes/items.py @@ -7,7 +7,7 @@ from app.api.deps import CurrentUser, SessionDep from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message -router = APIRouter() +router = APIRouter(prefix="/items", tags=["items"]) @router.get("/", response_model=ItemsPublic) diff --git a/backend/app/api/routes/login.py b/backend/app/api/routes/login.py index fe7e94d5c1..980c66f86f 100644 --- a/backend/app/api/routes/login.py +++ b/backend/app/api/routes/login.py @@ -18,7 +18,7 @@ verify_password_reset_token, ) -router = APIRouter() +router = APIRouter(tags=["login"]) @router.post("/login/access-token") diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index c636b094ee..0e8b5a87f8 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -26,7 +26,7 @@ ) from app.utils import generate_new_account_email, send_email -router = APIRouter() +router = APIRouter(prefix="/users", tags=["users"]) @router.get( diff --git a/backend/app/api/routes/utils.py b/backend/app/api/routes/utils.py index a73b80d761..fc093419b3 100644 --- a/backend/app/api/routes/utils.py +++ b/backend/app/api/routes/utils.py @@ -5,7 +5,7 @@ from app.models import Message from app.utils import generate_test_email, send_email -router = APIRouter() +router = APIRouter(prefix="/utils", tags=["utils"]) @router.post( From a07e0f36c05f8990f9109bf27469e68910a53d43 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 2 Dec 2024 12:04:22 +0000 Subject: [PATCH 732/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 470e3ee825..4e1608f198 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ ### Refactors +* 🎨 Move `prefix` and `tags` to routers. PR [#1439](https://github.com/fastapi/full-stack-fastapi-template/pull/1439) by [@patrick91](https://github.com/patrick91). * ♻️ Remove modify id script in favor of openapi-ts config. PR [#1434](https://github.com/fastapi/full-stack-fastapi-template/pull/1434) by [@patrick91](https://github.com/patrick91). * 👷 Improve Playwright CI speed: sharding (paralel runs), run in Docker to use cache, use env vars. PR [#1405](https://github.com/fastapi/full-stack-fastapi-template/pull/1405) by [@tiangolo](https://github.com/tiangolo). * ♻️ Add PaginationFooter component. PR [#1381](https://github.com/fastapi/full-stack-fastapi-template/pull/1381) by [@saltie2193](https://github.com/saltie2193). From c201db440aff96f17e05c2d9775f1227a58d3087 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:59:36 +0000 Subject: [PATCH 733/771] =?UTF-8?q?=E2=AC=86=20Bump=20astral-sh/setup-uv?= =?UTF-8?q?=20from=203=20to=204=20(#1433)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 3 to 4. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v3...v4) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/generate-client.yml | 2 +- .github/workflows/lint-backend.yml | 2 +- .github/workflows/playwright.yml | 2 +- .github/workflows/test-backend.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index b1787b0489..737fe0d6d9 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -27,7 +27,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v3 + uses: astral-sh/setup-uv@v4 with: version: "0.4.15" enable-cache: true diff --git a/.github/workflows/lint-backend.yml b/.github/workflows/lint-backend.yml index c7d7f2f8e4..0da41ffe2d 100644 --- a/.github/workflows/lint-backend.yml +++ b/.github/workflows/lint-backend.yml @@ -20,7 +20,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v3 + uses: astral-sh/setup-uv@v4 with: version: "0.4.15" enable-cache: true diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index b78f32c623..ccfeac0198 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -60,7 +60,7 @@ jobs: with: limit-access-to-actor: true - name: Install uv - uses: astral-sh/setup-uv@v3 + uses: astral-sh/setup-uv@v4 with: version: "0.4.15" enable-cache: true diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 2244836a01..dbb96f7553 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -20,7 +20,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v3 + uses: astral-sh/setup-uv@v4 with: version: "0.4.15" enable-cache: true From 39572e6fae72494e027b1c4e06e9a617a0027ecd Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 10 Dec 2024 09:59:57 +0000 Subject: [PATCH 734/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 4e1608f198..7096520c43 100644 --- a/release-notes.md +++ b/release-notes.md @@ -25,6 +25,7 @@ ### Internal +* ⬆ Bump astral-sh/setup-uv from 3 to 4. PR [#1433](https://github.com/fastapi/full-stack-fastapi-template/pull/1433) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump tiangolo/latest-changes from 0.3.1 to 0.3.2. PR [#1418](https://github.com/fastapi/full-stack-fastapi-template/pull/1418) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update issue manager workflow. PR [#1398](https://github.com/fastapi/full-stack-fastapi-template/pull/1398) by [@alejsdev](https://github.com/alejsdev). * 👷 Fix smokeshow, checkout files on CI. PR [#1395](https://github.com/fastapi/full-stack-fastapi-template/pull/1395) by [@tiangolo](https://github.com/tiangolo). From 50647c404e5d280626441e3661a76e7b3584a5a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 10:59:31 +0000 Subject: [PATCH 735/771] =?UTF-8?q?=E2=AC=86=20Bump=20astral-sh/setup-uv?= =?UTF-8?q?=20from=204=20to=205=20(#1453)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4 to 5. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v4...v5) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/generate-client.yml | 2 +- .github/workflows/lint-backend.yml | 2 +- .github/workflows/playwright.yml | 2 +- .github/workflows/test-backend.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml index 737fe0d6d9..304363ce96 100644 --- a/.github/workflows/generate-client.yml +++ b/.github/workflows/generate-client.yml @@ -27,7 +27,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true diff --git a/.github/workflows/lint-backend.yml b/.github/workflows/lint-backend.yml index 0da41ffe2d..a6e536bffe 100644 --- a/.github/workflows/lint-backend.yml +++ b/.github/workflows/lint-backend.yml @@ -20,7 +20,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ccfeac0198..5b13c58689 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -60,7 +60,7 @@ jobs: with: limit-access-to-actor: true - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index dbb96f7553..cbbb78de46 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -20,7 +20,7 @@ jobs: with: python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true From 2be48d672262a1e6a9749f6d55f0efed99c8e1df Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 21 Dec 2024 10:59:48 +0000 Subject: [PATCH 736/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 7096520c43..679fd82d4a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -25,6 +25,7 @@ ### Internal +* ⬆ Bump astral-sh/setup-uv from 4 to 5. PR [#1453](https://github.com/fastapi/full-stack-fastapi-template/pull/1453) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump astral-sh/setup-uv from 3 to 4. PR [#1433](https://github.com/fastapi/full-stack-fastapi-template/pull/1433) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump tiangolo/latest-changes from 0.3.1 to 0.3.2. PR [#1418](https://github.com/fastapi/full-stack-fastapi-template/pull/1418) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Update issue manager workflow. PR [#1398](https://github.com/fastapi/full-stack-fastapi-template/pull/1398) by [@alejsdev](https://github.com/alejsdev). From e20aada4e42a937f7cc259b0feba5b3c09565292 Mon Sep 17 00:00:00 2001 From: Ayoub Benaissa Date: Sat, 21 Dec 2024 12:00:44 +0100 Subject: [PATCH 737/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20frontend/README?= =?UTF-8?q?.md=20to=20also=20remove=20Playwright=20when=20removing=20Front?= =?UTF-8?q?end=20(#1452)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/README.md b/frontend/README.md index fde8267842..569af503b4 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -59,7 +59,7 @@ If you are developing an API-only app and want to remove the frontend, you can d * In the `docker-compose.yml` file, remove the whole service / section `frontend`. -* In the `docker-compose.override.yml` file, remove the whole service / section `frontend`. +* In the `docker-compose.override.yml` file, remove the whole service / section `frontend` and `playwright`. Done, you have a frontend-less (api-only) app. 🤓 From 49b52af26c5921be433515befbd545215c709386 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 21 Dec 2024 11:01:03 +0000 Subject: [PATCH 738/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.md b/release-notes.md index 679fd82d4a..6d5edfb673 100644 --- a/release-notes.md +++ b/release-notes.md @@ -20,6 +20,7 @@ ### Docs +* 📝 Update `frontend/README.md` to also remove Playwright when removing Frontend. PR [#1452](https://github.com/fastapi/full-stack-fastapi-template/pull/1452) by [@youben11](https://github.com/youben11). * 📝 Update `deployment.md`, instructions to install GitHub Runner in non-root VMs. PR [#1412](https://github.com/fastapi/full-stack-fastapi-template/pull/1412) by [@tiangolo](https://github.com/tiangolo). * 📝 Add MailCatcher to `development.md`. PR [#1387](https://github.com/fastapi/full-stack-fastapi-template/pull/1387) by [@tobiase](https://github.com/tobiase). From 0477610e2c02a4e58479a7ade3830f16c5cdaca8 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Sun, 5 Jan 2025 14:57:42 +0000 Subject: [PATCH 739/771] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20Dockerfil?= =?UTF-8?q?e=20to=20use=20uv=20version=200.5.11=20(#1454)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 9d6e699f30..44c53f0365 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /app/ # Install uv # Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv -COPY --from=ghcr.io/astral-sh/uv:0.4.15 /uv /bin/uv +COPY --from=ghcr.io/astral-sh/uv:0.5.11 /uv /uvx /bin/ # Place executables in the environment at the front of the path # Ref: https://docs.astral.sh/uv/guides/integration/docker/#using-the-environment From ced4c05e8d51f4b7cb7436d64ca205d06d8c477d Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 5 Jan 2025 14:58:03 +0000 Subject: [PATCH 740/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index 6d5edfb673..bdc024ed78 100644 --- a/release-notes.md +++ b/release-notes.md @@ -18,6 +18,10 @@ * 🔧 Add `ENV PYTHONUNBUFFERED=1` to log output directly to Docker. PR [#1378](https://github.com/fastapi/full-stack-fastapi-template/pull/1378) by [@tiangolo](https://github.com/tiangolo). * 💡 Remove unnecessary comment. PR [#1260](https://github.com/fastapi/full-stack-fastapi-template/pull/1260) by [@sebhani](https://github.com/sebhani). +### Upgrades + +* ⬆️ Update Dockerfile to use uv version 0.5.11. PR [#1454](https://github.com/fastapi/full-stack-fastapi-template/pull/1454) by [@alejsdev](https://github.com/alejsdev). + ### Docs * 📝 Update `frontend/README.md` to also remove Playwright when removing Frontend. PR [#1452](https://github.com/fastapi/full-stack-fastapi-template/pull/1452) by [@youben11](https://github.com/youben11). From fe000a424d61b31589d9007c80e1e46bb1827134 Mon Sep 17 00:00:00 2001 From: Boriss Jerjomkin <42120838+wesenbergg@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:32:57 +0200 Subject: [PATCH 741/771] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=94=A7=20Rep?= =?UTF-8?q?lace=20correct=20value=20for=20'htmlFor'=20(#1456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/routes/signup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index b021e73698..80396c5078 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -96,7 +96,7 @@ function SignUp() { )} - + Email Date: Mon, 6 Jan 2025 11:33:15 +0000 Subject: [PATCH 742/771] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes.md b/release-notes.md index bdc024ed78..7a55e34c8a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -7,6 +7,10 @@ * ✨ Add private, local only, API for usage in E2E tests. PR [#1429](https://github.com/fastapi/full-stack-fastapi-template/pull/1429) by [@patrick91](https://github.com/patrick91). * ✨ Migrate to latest openapi-ts. PR [#1430](https://github.com/fastapi/full-stack-fastapi-template/pull/1430) by [@patrick91](https://github.com/patrick91). +### Fixes + +* 🧑‍🔧 Replace correct value for 'htmlFor'. PR [#1456](https://github.com/fastapi/full-stack-fastapi-template/pull/1456) by [@wesenbergg](https://github.com/wesenbergg). + ### Refactors * 🎨 Move `prefix` and `tags` to routers. PR [#1439](https://github.com/fastapi/full-stack-fastapi-template/pull/1439) by [@patrick91](https://github.com/patrick91). From c71fdc012f6893a5d75c71a53caf6afa24e44387 Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Wed, 29 Jan 2025 09:36:15 +0100 Subject: [PATCH 743/771] Path models and path routes are included in backend. Working towards a path creation and editing progress that is parallel to that of 'items'. --- backend/app/api/main.py | 3 +- backend/app/api/routes/paths.py | 142 ++++++++++++++++++++++++++++++++ backend/app/models.py | 54 +++++++++++- frontend/src/main.tsx | 2 +- 4 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 backend/app/api/routes/paths.py diff --git a/backend/app/api/main.py b/backend/app/api/main.py index eac18c8e8f..6f9b3ca0c3 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.api.routes import items, login, private, users, utils +from app.api.routes import items, login, private, users, utils, paths from app.core.config import settings api_router = APIRouter() @@ -8,6 +8,7 @@ api_router.include_router(users.router) api_router.include_router(utils.router) api_router.include_router(items.router) +api_router.include_router(paths.router) if settings.ENVIRONMENT == "local": diff --git a/backend/app/api/routes/paths.py b/backend/app/api/routes/paths.py new file mode 100644 index 0000000000..e9f85532f2 --- /dev/null +++ b/backend/app/api/routes/paths.py @@ -0,0 +1,142 @@ +import uuid +from typing import Any +from datetime import datetime + +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session, select, func + +from app.api.deps import CurrentUser, SessionDep +from app.models import ( + Path, Step, PathCreate, PathResponse, StepCreate, User +) + +router = APIRouter(prefix="/paths", tags=["paths"]) + +@router.get("/", response_model=list[PathResponse]) +async def list_paths( + session: SessionDep, + current_user: CurrentUser, + skip: int = 0, + limit: int = 100, +) -> Any: + """ + Retrieve paths with their steps. + """ + paths = session.exec( + select(Path) + .where(Path.creator_id == current_user.id) + .offset(skip) + .limit(limit) + ).all() + return paths + +@router.get("/{path_id}", response_model=PathResponse) +async def get_path( + *, + session: SessionDep, + current_user: CurrentUser, + path_id: uuid.UUID, +) -> Any: + """ + Get path by ID with all its steps. + """ + path = session.get(Path, path_id) + if not path: + raise HTTPException(status_code=404, detail="Path not found") + if path.creator_id != current_user.id: + raise HTTPException(status_code=403, detail="Not enough permissions") + return path + +@router.post("/", response_model=PathResponse) +async def create_path( + *, + session: SessionDep, + current_user: CurrentUser, + path_in: PathCreate, +) -> Any: + """ + Create a new path with its steps. + """ + path = Path( + creator_id=current_user.id, + title=path_in.title, + path_summary=path_in.path_summary, + ) + session.add(path) + session.flush() # This assigns the ID to the path + + # Create steps with proper ordering + for step_data in path_in.steps: + step = Step( + path_id=path.id, + number=step_data.number, + role_prompt=step_data.role_prompt, + validation_prompt=step_data.validation_prompt, + exposition=step_data.exposition, + ) + session.add(step) + + session.commit() + session.refresh(path) + return path + +@router.put("/{path_id}", response_model=PathResponse) +async def update_path( + *, + session: SessionDep, + current_user: CurrentUser, + path_id: uuid.UUID, + path_in: PathCreate, +) -> Any: + """ + Update a path and all its steps. Steps will be completely replaced. + """ + path = session.get(Path, path_id) + if not path: + raise HTTPException(status_code=404, detail="Path not found") + if path.creator_id != current_user.id: + raise HTTPException(status_code=403, detail="Not enough permissions") + + # Update path attributes + path.title = path_in.title + path.path_summary = path_in.path_summary + path.updated_at = datetime.utcnow() + + # Replace all steps + session.query(Step).filter(Step.path_id == path_id).delete() + + # Create new steps + for step_data in path_in.steps: + step = Step( + path_id=path_id, + number=step_data.number, + role_prompt=step_data.role_prompt, + validation_prompt=step_data.validation_prompt, + exposition=step_data.exposition, + ) + session.add(step) + + session.add(path) + session.commit() + session.refresh(path) + return path + +@router.delete("/{path_id}") +async def delete_path( + *, + session: SessionDep, + current_user: CurrentUser, + path_id: uuid.UUID, +) -> dict: + """ + Delete a path and all its steps. + """ + path = session.get(Path, path_id) + if not path: + raise HTTPException(status_code=404, detail="Path not found") + if path.creator_id != current_user.id: + raise HTTPException(status_code=403, detail="Not enough permissions") + + session.delete(path) + session.commit() + return {"message": "Path deleted successfully"} \ No newline at end of file diff --git a/backend/app/models.py b/backend/app/models.py index 90ef5559e3..e463bffa16 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -1,7 +1,8 @@ import uuid -from pydantic import EmailStr +from pydantic import EmailStr, HttpUrl from sqlmodel import Field, Relationship, SQLModel +from datetime import datetime # Shared properties @@ -112,3 +113,54 @@ class TokenPayload(SQLModel): class NewPassword(SQLModel): token: str new_password: str = Field(min_length=8, max_length=40) + +# TreadEd specific models +## Database models +class Path(SQLModel, table=True): + id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + creator_id: uuid.UUID = Field(foreign_key="user.id", nullable=False) + title: str = Field(max_length=255) + path_summary: str | None = Field(default=None, max_length=255) + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + steps: list["Step"] = Relationship(back_populates="path", cascade_delete=True) + +class YoutubeExposition(SQLModel): + url: HttpUrl + start_time: int | None = Field(default=None, ge=0) + end_time: int | None = Field(default=None, ge=0) + +class Step(SQLModel, table=True): + id: int = Field(default=None, primary_key=True) + path_id: uuid.UUID = Field(foreign_key="path.id", nullable=False) + number: int = Field(ge=0) + validation_scope: int | None = None + role_prompt: dict | None = Field(default=None) + validation_prompt: dict | None = Field(default=None) + exposition: YoutubeExposition | None = Field(default=None) + path: Path = Relationship(back_populates="steps") + +# API Models +class PathCreate(SQLModel): + title: str + path_summary: str | None = None + steps: list[StepCreate] # Make steps required for path creation + +class PathResponse(SQLModel): + id: uuid.UUID + title: str + path_summary: str | None + steps: list[StepResponse] # Always include steps with path + +class StepCreate(SQLModel): + number: int + role_prompt: dict | None = None + validation_prompt: dict | None = Field(default=None) + exposition: YoutubeExposition | None = None + +class StepResponse(SQLModel): + id: int + number: int + role_prompt: dict | None = None + validation_prompt: dict | None = Field(default=None) + exposition: YoutubeExposition | None = None \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index afc904538b..7193a6a715 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,10 @@ +import React, { StrictMode } from "react" import { ChakraProvider } from "@chakra-ui/react" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { RouterProvider, createRouter } from "@tanstack/react-router" import ReactDOM from "react-dom/client" import { routeTree } from "./routeTree.gen" -import { StrictMode } from "react" import { OpenAPI } from "./client" import theme from "./theme" From a697d3ae57116f979685a295fe713ab1bf47de9e Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Thu, 30 Jan 2025 21:28:44 +0100 Subject: [PATCH 744/771] minor changes to configuration --- SECURITY.md | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 0045fb8182..0000000000 --- a/SECURITY.md +++ /dev/null @@ -1,29 +0,0 @@ -# Security Policy - -Security is very important for this project and its community. 🔒 - -Learn more about it below. 👇 - -## Versions - -The latest version or release is supported. - -You are encouraged to write tests for your application and update your versions frequently after ensuring that your tests are passing. This way you will benefit from the latest features, bug fixes, and **security fixes**. - -## Reporting a Vulnerability - -If you think you found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: security@tiangolo.com. Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue. - -I (the author, [@tiangolo](https://twitter.com/tiangolo)) will review it thoroughly and get back to you. - -## Public Discussions - -Please restrain from publicly discussing a potential security vulnerability. 🙊 - -It's better to discuss privately and try to find a solution first, to limit the potential impact as much as possible. - ---- - -Thanks for your help! - -The community and I thank you for that. 🙇 From 32dbf36295bc8803e61136302819bd8768fc1a59 Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Fri, 31 Jan 2025 17:27:39 +0100 Subject: [PATCH 745/771] Add missing path and step models migration -Connect mail setup -Integrate paths and steps into backend (complete) -Integrate paths and steps into the frontend (incomplete) --- .../ea8d60701d49_add_path_and_step_models.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py diff --git a/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py b/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py new file mode 100644 index 0000000000..cbddfdf2e2 --- /dev/null +++ b/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py @@ -0,0 +1,54 @@ +"""add path and step models + +Revision ID: ea8d60701d49 +Revises: e2412789c190 +Create Date: 2025-01-31 17:08:00.000000 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision: str = 'ea8d60701d49' +down_revision: Union[str, None] = 'e2412789c190' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Create path table + op.create_table( + 'path', + sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column('creator_id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('path_summary', sa.String(length=255), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['creator_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + + # Create step table and sequence + op.create_sequence('step_id_seq') + op.create_table( + 'step', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('path_id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column('number', sa.Integer(), nullable=False), + sa.Column('role_prompt', sa.String(), nullable=True), + sa.Column('validation_prompt', sa.String(), nullable=True), + sa.Column('exposition_json', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['path_id'], ['path.id'], ), + sa.PrimaryKeyConstraint('id') + ) + + +def downgrade() -> None: + op.drop_table('step') + op.drop_sequence('step_id_seq') + op.drop_table('path') From 7ba177d265990a2511d4dec741286075f0df3bff Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Fri, 31 Jan 2025 17:29:05 +0100 Subject: [PATCH 746/771] missed most files from the previous commit --- backend/app/api/routes/paths.py | 87 +- backend/app/models.py | 66 +- frontend/src/client/core/ApiError.ts | 38 +- frontend/src/client/core/ApiRequestOptions.ts | 40 +- frontend/src/client/core/ApiResult.ts | 12 +- frontend/src/client/core/CancelablePromise.ts | 236 ++-- frontend/src/client/core/OpenAPI.ts | 76 +- frontend/src/client/core/request.ts | 660 +++++----- frontend/src/client/index.ts | 10 +- frontend/src/client/sdk.gen.ts | 1078 +++++++++-------- frontend/src/client/types.gen.ts | 337 ++++-- .../src/components/Common/SidebarItems.tsx | 3 +- frontend/src/routeTree.gen.ts | 11 + frontend/src/routes/_layout/paths.tsx | 143 +++ schema.sql | 213 ++++ 15 files changed, 1771 insertions(+), 1239 deletions(-) create mode 100644 frontend/src/routes/_layout/paths.tsx create mode 100644 schema.sql diff --git a/backend/app/api/routes/paths.py b/backend/app/api/routes/paths.py index e9f85532f2..3a571411bf 100644 --- a/backend/app/api/routes/paths.py +++ b/backend/app/api/routes/paths.py @@ -2,17 +2,18 @@ from typing import Any from datetime import datetime -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, HTTPException from sqlmodel import Session, select, func from app.api.deps import CurrentUser, SessionDep from app.models import ( - Path, Step, PathCreate, PathResponse, StepCreate, User + Path, Step, PathCreate, PathPublic, PathInList, PathsPublic, + utc_now ) router = APIRouter(prefix="/paths", tags=["paths"]) -@router.get("/", response_model=list[PathResponse]) +@router.get("/", response_model=PathsPublic) async def list_paths( session: SessionDep, current_user: CurrentUser, @@ -20,17 +21,39 @@ async def list_paths( limit: int = 100, ) -> Any: """ - Retrieve paths with their steps. + Retrieve paths without loading steps. """ - paths = session.exec( + # Get total count + count_statement = ( + select(func.count()) + .select_from(Path) + .where(Path.creator_id == current_user.id) + ) + count = session.exec(count_statement).one() + + # Get paths without loading steps + statement = ( select(Path) .where(Path.creator_id == current_user.id) .offset(skip) .limit(limit) - ).all() - return paths + ) + paths = session.exec(statement).all() + + # Convert to PathInList responses + path_responses = [ + PathInList( + id=path.id, + title=path.title, + path_summary=path.path_summary, + created_at=path.created_at, + ) + for path in paths + ] + + return PathsPublic(data=path_responses, count=count) -@router.get("/{path_id}", response_model=PathResponse) +@router.get("/{path_id}", response_model=PathPublic) async def get_path( *, session: SessionDep, @@ -40,14 +63,20 @@ async def get_path( """ Get path by ID with all its steps. """ - path = session.get(Path, path_id) + # Use select to explicitly join with steps + statement = ( + select(Path) + .where(Path.id == path_id) + .where(Path.creator_id == current_user.id) + ) + path = session.exec(statement).first() + if not path: raise HTTPException(status_code=404, detail="Path not found") - if path.creator_id != current_user.id: - raise HTTPException(status_code=403, detail="Not enough permissions") + return path -@router.post("/", response_model=PathResponse) +@router.post("/", response_model=PathPublic) async def create_path( *, session: SessionDep, @@ -67,12 +96,17 @@ async def create_path( # Create steps with proper ordering for step_data in path_in.steps: + # Convert YoutubeExposition to JSON if present + exposition_json = None + if step_data.exposition: + exposition_json = step_data.exposition.model_dump_json() + step = Step( path_id=path.id, number=step_data.number, role_prompt=step_data.role_prompt, validation_prompt=step_data.validation_prompt, - exposition=step_data.exposition, + exposition_json=exposition_json, ) session.add(step) @@ -80,7 +114,7 @@ async def create_path( session.refresh(path) return path -@router.put("/{path_id}", response_model=PathResponse) +@router.put("/{path_id}", response_model=PathPublic) async def update_path( *, session: SessionDep, @@ -97,26 +131,31 @@ async def update_path( if path.creator_id != current_user.id: raise HTTPException(status_code=403, detail="Not enough permissions") - # Update path attributes + # Update path fields path.title = path_in.title path.path_summary = path_in.path_summary - path.updated_at = datetime.utcnow() + path.updated_at = utc_now() - # Replace all steps - session.query(Step).filter(Step.path_id == path_id).delete() + # Delete existing steps + for step in path.steps: + session.delete(step) # Create new steps for step_data in path_in.steps: + # Convert YoutubeExposition to JSON if present + exposition_json = None + if step_data.exposition: + exposition_json = step_data.exposition.model_dump_json() + step = Step( - path_id=path_id, + path_id=path.id, number=step_data.number, role_prompt=step_data.role_prompt, validation_prompt=step_data.validation_prompt, - exposition=step_data.exposition, + exposition_json=exposition_json, ) session.add(step) - session.add(path) session.commit() session.refresh(path) return path @@ -127,7 +166,7 @@ async def delete_path( session: SessionDep, current_user: CurrentUser, path_id: uuid.UUID, -) -> dict: +) -> Any: """ Delete a path and all its steps. """ @@ -136,7 +175,7 @@ async def delete_path( raise HTTPException(status_code=404, detail="Path not found") if path.creator_id != current_user.id: raise HTTPException(status_code=403, detail="Not enough permissions") - - session.delete(path) + + session.delete(path) # This will cascade delete steps due to relationship session.commit() return {"message": "Path deleted successfully"} \ No newline at end of file diff --git a/backend/app/models.py b/backend/app/models.py index e463bffa16..ac13035e24 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -2,7 +2,7 @@ from pydantic import EmailStr, HttpUrl from sqlmodel import Field, Relationship, SQLModel -from datetime import datetime +from datetime import datetime, timezone # Shared properties @@ -116,13 +116,16 @@ class NewPassword(SQLModel): # TreadEd specific models ## Database models +def utc_now(): + return datetime.now(timezone.utc) + class Path(SQLModel, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) creator_id: uuid.UUID = Field(foreign_key="user.id", nullable=False) title: str = Field(max_length=255) path_summary: str | None = Field(default=None, max_length=255) - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=utc_now) + updated_at: datetime = Field(default_factory=utc_now) steps: list["Step"] = Relationship(back_populates="path", cascade_delete=True) class YoutubeExposition(SQLModel): @@ -134,33 +137,56 @@ class Step(SQLModel, table=True): id: int = Field(default=None, primary_key=True) path_id: uuid.UUID = Field(foreign_key="path.id", nullable=False) number: int = Field(ge=0) - validation_scope: int | None = None - role_prompt: dict | None = Field(default=None) - validation_prompt: dict | None = Field(default=None) - exposition: YoutubeExposition | None = Field(default=None) + role_prompt: str | None = Field(default=None) + validation_prompt: str | None = Field(default=None) + exposition_json: str | None = Field(default=None) path: Path = Relationship(back_populates="steps") # API Models +class StepCreate(SQLModel): + number: int + role_prompt: str | None = Field(default=None) + validation_prompt: str | None = Field(default=None) + exposition: YoutubeExposition | None = None + +class StepUpdate(SQLModel): + role_prompt: str | None = None + validation_prompt: str | None = None + exposition: YoutubeExposition | None = None + +class StepPublic(SQLModel): + id: int + number: int + role_prompt: str | None = Field(default=None) + validation_prompt: str | None = Field(default=None) + exposition: YoutubeExposition | None = None + +class StepInList(SQLModel): + id: int + number: int + exposition: YoutubeExposition | None = None # Include only what's needed for list view + class PathCreate(SQLModel): title: str path_summary: str | None = None steps: list[StepCreate] # Make steps required for path creation -class PathResponse(SQLModel): +class PathUpdate(SQLModel): + title: str | None = None + path_summary: str | None = None + +class PathPublic(SQLModel): id: uuid.UUID title: str path_summary: str | None - steps: list[StepResponse] # Always include steps with path + steps: list[StepPublic] # Always include steps with path -class StepCreate(SQLModel): - number: int - role_prompt: dict | None = None - validation_prompt: dict | None = Field(default=None) - exposition: YoutubeExposition | None = None +class PathInList(SQLModel): + id: uuid.UUID + title: str + path_summary: str | None + created_at: datetime -class StepResponse(SQLModel): - id: int - number: int - role_prompt: dict | None = None - validation_prompt: dict | None = Field(default=None) - exposition: YoutubeExposition | None = None \ No newline at end of file +class PathsPublic(SQLModel): + data: list[PathInList] + count: int \ No newline at end of file diff --git a/frontend/src/client/core/ApiError.ts b/frontend/src/client/core/ApiError.ts index 5499aa8f05..36675d288a 100644 --- a/frontend/src/client/core/ApiError.ts +++ b/frontend/src/client/core/ApiError.ts @@ -1,25 +1,21 @@ -import type { ApiRequestOptions } from "./ApiRequestOptions" -import type { ApiResult } from "./ApiResult" +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; export class ApiError extends Error { - public readonly url: string - public readonly status: number - public readonly statusText: string - public readonly body: unknown - public readonly request: ApiRequestOptions + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: unknown; + public readonly request: ApiRequestOptions; - constructor( - request: ApiRequestOptions, - response: ApiResult, - message: string, - ) { - super(message) + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); - this.name = "ApiError" - this.url = response.url - this.status = response.status - this.statusText = response.statusText - this.body = response.body - this.request = request - } -} + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} \ No newline at end of file diff --git a/frontend/src/client/core/ApiRequestOptions.ts b/frontend/src/client/core/ApiRequestOptions.ts index d1136f428b..939a0aa4c8 100644 --- a/frontend/src/client/core/ApiRequestOptions.ts +++ b/frontend/src/client/core/ApiRequestOptions.ts @@ -1,21 +1,21 @@ export type ApiRequestOptions = { - readonly body?: any - readonly cookies?: Record - readonly errors?: Record - readonly formData?: Record | any[] | Blob | File - readonly headers?: Record - readonly mediaType?: string - readonly method: - | "DELETE" - | "GET" - | "HEAD" - | "OPTIONS" - | "PATCH" - | "POST" - | "PUT" - readonly path?: Record - readonly query?: Record - readonly responseHeader?: string - readonly responseTransformer?: (data: unknown) => Promise - readonly url: string -} + readonly body?: any; + readonly cookies?: Record; + readonly errors?: Record; + readonly formData?: Record | any[] | Blob | File; + readonly headers?: Record; + readonly mediaType?: string; + readonly method: + | 'DELETE' + | 'GET' + | 'HEAD' + | 'OPTIONS' + | 'PATCH' + | 'POST' + | 'PUT'; + readonly path?: Record; + readonly query?: Record; + readonly responseHeader?: string; + readonly responseTransformer?: (data: unknown) => Promise; + readonly url: string; +}; \ No newline at end of file diff --git a/frontend/src/client/core/ApiResult.ts b/frontend/src/client/core/ApiResult.ts index f88b8c64f1..4c58e39138 100644 --- a/frontend/src/client/core/ApiResult.ts +++ b/frontend/src/client/core/ApiResult.ts @@ -1,7 +1,7 @@ export type ApiResult = { - readonly body: TData - readonly ok: boolean - readonly status: number - readonly statusText: string - readonly url: string -} + readonly body: TData; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly url: string; +}; \ No newline at end of file diff --git a/frontend/src/client/core/CancelablePromise.ts b/frontend/src/client/core/CancelablePromise.ts index f47db79eae..ccc082e8f2 100644 --- a/frontend/src/client/core/CancelablePromise.ts +++ b/frontend/src/client/core/CancelablePromise.ts @@ -1,126 +1,126 @@ export class CancelError extends Error { - constructor(message: string) { - super(message) - this.name = "CancelError" - } - - public get isCancelled(): boolean { - return true - } + constructor(message: string) { + super(message); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } } export interface OnCancel { - readonly isResolved: boolean - readonly isRejected: boolean - readonly isCancelled: boolean + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; - (cancelHandler: () => void): void + (cancelHandler: () => void): void; } export class CancelablePromise implements Promise { - private _isResolved: boolean - private _isRejected: boolean - private _isCancelled: boolean - readonly cancelHandlers: (() => void)[] - readonly promise: Promise - private _resolve?: (value: T | PromiseLike) => void - private _reject?: (reason?: unknown) => void - - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: unknown) => void, - onCancel: OnCancel, - ) => void, - ) { - this._isResolved = false - this._isRejected = false - this._isCancelled = false - this.cancelHandlers = [] - this.promise = new Promise((resolve, reject) => { - this._resolve = resolve - this._reject = reject - - const onResolve = (value: T | PromiseLike): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return - } - this._isResolved = true - if (this._resolve) this._resolve(value) - } - - const onReject = (reason?: unknown): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return - } - this._isRejected = true - if (this._reject) this._reject(reason) - } - - const onCancel = (cancelHandler: () => void): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return - } - this.cancelHandlers.push(cancelHandler) - } - - Object.defineProperty(onCancel, "isResolved", { - get: (): boolean => this._isResolved, - }) - - Object.defineProperty(onCancel, "isRejected", { - get: (): boolean => this._isRejected, - }) - - Object.defineProperty(onCancel, "isCancelled", { - get: (): boolean => this._isCancelled, - }) - - return executor(onResolve, onReject, onCancel as OnCancel) - }) - } - - get [Symbol.toStringTag]() { - return "Cancellable Promise" - } - - public then( - onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, - ): Promise { - return this.promise.then(onFulfilled, onRejected) - } - - public catch( - onRejected?: ((reason: unknown) => TResult | PromiseLike) | null, - ): Promise { - return this.promise.catch(onRejected) - } - - public finally(onFinally?: (() => void) | null): Promise { - return this.promise.finally(onFinally) - } - - public cancel(): void { - if (this._isResolved || this._isRejected || this._isCancelled) { - return - } - this._isCancelled = true - if (this.cancelHandlers.length) { - try { - for (const cancelHandler of this.cancelHandlers) { - cancelHandler() - } - } catch (error) { - console.warn("Cancellation threw an error", error) - return - } - } - this.cancelHandlers.length = 0 - if (this._reject) this._reject(new CancelError("Request aborted")) - } - - public get isCancelled(): boolean { - return this._isCancelled - } -} + private _isResolved: boolean; + private _isRejected: boolean; + private _isCancelled: boolean; + readonly cancelHandlers: (() => void)[]; + readonly promise: Promise; + private _resolve?: (value: T | PromiseLike) => void; + private _reject?: (reason?: unknown) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: unknown) => void, + onCancel: OnCancel + ) => void + ) { + this._isResolved = false; + this._isRejected = false; + this._isCancelled = false; + this.cancelHandlers = []; + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + + const onResolve = (value: T | PromiseLike): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isResolved = true; + if (this._resolve) this._resolve(value); + }; + + const onReject = (reason?: unknown): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isRejected = true; + if (this._reject) this._reject(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this.cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this._isResolved, + }); + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this._isRejected, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this._isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise"; + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null + ): Promise { + return this.promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: unknown) => TResult | PromiseLike) | null + ): Promise { + return this.promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.promise.finally(onFinally); + } + + public cancel(): void { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isCancelled = true; + if (this.cancelHandlers.length) { + try { + for (const cancelHandler of this.cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn('Cancellation threw an error', error); + return; + } + } + this.cancelHandlers.length = 0; + if (this._reject) this._reject(new CancelError('Request aborted')); + } + + public get isCancelled(): boolean { + return this._isCancelled; + } +} \ No newline at end of file diff --git a/frontend/src/client/core/OpenAPI.ts b/frontend/src/client/core/OpenAPI.ts index e99068ea2e..74f92b4085 100644 --- a/frontend/src/client/core/OpenAPI.ts +++ b/frontend/src/client/core/OpenAPI.ts @@ -1,57 +1,57 @@ -import type { AxiosRequestConfig, AxiosResponse } from "axios" -import type { ApiRequestOptions } from "./ApiRequestOptions" +import type { AxiosRequestConfig, AxiosResponse } from 'axios'; +import type { ApiRequestOptions } from './ApiRequestOptions'; -type Headers = Record -type Middleware = (value: T) => T | Promise -type Resolver = (options: ApiRequestOptions) => Promise +type Headers = Record; +type Middleware = (value: T) => T | Promise; +type Resolver = (options: ApiRequestOptions) => Promise; export class Interceptors { - _fns: Middleware[] + _fns: Middleware[]; constructor() { - this._fns = [] + this._fns = []; } eject(fn: Middleware): void { - const index = this._fns.indexOf(fn) + const index = this._fns.indexOf(fn); if (index !== -1) { - this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)] + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; } } use(fn: Middleware): void { - this._fns = [...this._fns, fn] + this._fns = [...this._fns, fn]; } } export type OpenAPIConfig = { - BASE: string - CREDENTIALS: "include" | "omit" | "same-origin" - ENCODE_PATH?: ((path: string) => string) | undefined - HEADERS?: Headers | Resolver | undefined - PASSWORD?: string | Resolver | undefined - TOKEN?: string | Resolver | undefined - USERNAME?: string | Resolver | undefined - VERSION: string - WITH_CREDENTIALS: boolean - interceptors: { - request: Interceptors - response: Interceptors - } -} + BASE: string; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + ENCODE_PATH?: ((path: string) => string) | undefined; + HEADERS?: Headers | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + VERSION: string; + WITH_CREDENTIALS: boolean; + interceptors: { + request: Interceptors; + response: Interceptors; + }; +}; export const OpenAPI: OpenAPIConfig = { - BASE: "", - CREDENTIALS: "include", - ENCODE_PATH: undefined, - HEADERS: undefined, - PASSWORD: undefined, - TOKEN: undefined, - USERNAME: undefined, - VERSION: "0.1.0", - WITH_CREDENTIALS: false, - interceptors: { - request: new Interceptors(), - response: new Interceptors(), - }, -} + BASE: '', + CREDENTIALS: 'include', + ENCODE_PATH: undefined, + HEADERS: undefined, + PASSWORD: undefined, + TOKEN: undefined, + USERNAME: undefined, + VERSION: '0.1.0', + WITH_CREDENTIALS: false, + interceptors: { + request: new Interceptors(), + response: new Interceptors(), + }, +}; \ No newline at end of file diff --git a/frontend/src/client/core/request.ts b/frontend/src/client/core/request.ts index 8b42272b93..ecc2e393cd 100644 --- a/frontend/src/client/core/request.ts +++ b/frontend/src/client/core/request.ts @@ -1,325 +1,301 @@ -import axios from "axios" -import type { - AxiosError, - AxiosRequestConfig, - AxiosResponse, - AxiosInstance, -} from "axios" - -import { ApiError } from "./ApiError" -import type { ApiRequestOptions } from "./ApiRequestOptions" -import type { ApiResult } from "./ApiResult" -import { CancelablePromise } from "./CancelablePromise" -import type { OnCancel } from "./CancelablePromise" -import type { OpenAPIConfig } from "./OpenAPI" +import axios from 'axios'; +import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; export const isString = (value: unknown): value is string => { - return typeof value === "string" -} + return typeof value === 'string'; +}; export const isStringWithValue = (value: unknown): value is string => { - return isString(value) && value !== "" -} + return isString(value) && value !== ''; +}; export const isBlob = (value: any): value is Blob => { - return value instanceof Blob -} + return value instanceof Blob; +}; export const isFormData = (value: unknown): value is FormData => { - return value instanceof FormData -} + return value instanceof FormData; +}; export const isSuccess = (status: number): boolean => { - return status >= 200 && status < 300 -} + return status >= 200 && status < 300; +}; export const base64 = (str: string): string => { - try { - return btoa(str) - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString("base64") - } -} + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; export const getQueryString = (params: Record): string => { - const qs: string[] = [] + const qs: string[] = []; - const append = (key: string, value: unknown) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) - } + const append = (key: string, value: unknown) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; - const encodePair = (key: string, value: unknown) => { - if (value === undefined || value === null) { - return - } + const encodePair = (key: string, value: unknown) => { + if (value === undefined || value === null) { + return; + } - if (value instanceof Date) { - append(key, value.toISOString()) - } else if (Array.isArray(value)) { - value.forEach((v) => encodePair(key, v)) - } else if (typeof value === "object") { - Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)) - } else { - append(key, value) - } - } + if (value instanceof Date) { + append(key, value.toISOString()); + } else if (Array.isArray(value)) { + value.forEach(v => encodePair(key, v)); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); + } else { + append(key, value); + } + }; - Object.entries(params).forEach(([key, value]) => encodePair(key, value)) + Object.entries(params).forEach(([key, value]) => encodePair(key, value)); - return qs.length ? `?${qs.join("&")}` : "" -} + return qs.length ? `?${qs.join('&')}` : ''; +}; const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI - - const path = options.url - .replace("{api-version}", config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])) - } - return substring - }) - - const url = config.BASE + path - return options.query ? url + getQueryString(options.query) : url -} - -export const getFormData = ( - options: ApiRequestOptions, -): FormData | undefined => { - if (options.formData) { - const formData = new FormData() - - const process = (key: string, value: unknown) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value) - } else { - formData.append(key, JSON.stringify(value)) - } - } - - Object.entries(options.formData) - .filter(([, value]) => value !== undefined && value !== null) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach((v) => process(key, v)) - } else { - process(key, value) - } - }) - - return formData - } - return undefined -} - -type Resolver = (options: ApiRequestOptions) => Promise - -export const resolve = async ( - options: ApiRequestOptions, - resolver?: T | Resolver, -): Promise => { - if (typeof resolver === "function") { - return (resolver as Resolver)(options) - } - return resolver -} - -export const getHeaders = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, -): Promise> => { - const [token, username, password, additionalHeaders] = await Promise.all([ - // @ts-ignore - resolve(options, config.TOKEN), - // @ts-ignore - resolve(options, config.USERNAME), - // @ts-ignore - resolve(options, config.PASSWORD), - // @ts-ignore - resolve(options, config.HEADERS), - ]) - - const headers = Object.entries({ - Accept: "application/json", - ...additionalHeaders, - ...options.headers, - }) - .filter(([, value]) => value !== undefined && value !== null) - .reduce( - (headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), - {} as Record, - ) - - if (isStringWithValue(token)) { - headers["Authorization"] = `Bearer ${token}` - } - - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`) - headers["Authorization"] = `Basic ${credentials}` - } - - if (options.body !== undefined) { - if (options.mediaType) { - headers["Content-Type"] = options.mediaType - } else if (isBlob(options.body)) { - headers["Content-Type"] = options.body.type || "application/octet-stream" - } else if (isString(options.body)) { - headers["Content-Type"] = "text/plain" - } else if (!isFormData(options.body)) { - headers["Content-Type"] = "application/json" - } - } else if (options.formData !== undefined) { - if (options.mediaType) { - headers["Content-Type"] = options.mediaType - } - } - - return headers -} + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = config.BASE + path; + return options.query ? url + getQueryString(options.query) : url; +}; + +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: unknown) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([, value]) => value !== undefined && value !== null) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver = (options: ApiRequestOptions) => Promise; + +export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +}; + +export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise> => { + const [token, username, password, additionalHeaders] = await Promise.all([ + // @ts-ignore + resolve(options, config.TOKEN), + // @ts-ignore + resolve(options, config.USERNAME), + // @ts-ignore + resolve(options, config.PASSWORD), + // @ts-ignore + resolve(options, config.HEADERS), + ]); + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([, value]) => value !== undefined && value !== null) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } else if (options.formData !== undefined) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } + } + + return headers; +}; export const getRequestBody = (options: ApiRequestOptions): unknown => { - if (options.body) { - return options.body - } - return undefined -} + if (options.body) { + return options.body; + } + return undefined; +}; export const sendRequest = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: unknown, - formData: FormData | undefined, - headers: Record, - onCancel: OnCancel, - axiosClient: AxiosInstance, + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: unknown, + formData: FormData | undefined, + headers: Record, + onCancel: OnCancel, + axiosClient: AxiosInstance ): Promise> => { - const controller = new AbortController() - - let requestConfig: AxiosRequestConfig = { - data: body ?? formData, - headers, - method: options.method, - signal: controller.signal, - url, - withCredentials: config.WITH_CREDENTIALS, - } - - onCancel(() => controller.abort()) - - for (const fn of config.interceptors.request._fns) { - requestConfig = await fn(requestConfig) - } - - try { - return await axiosClient.request(requestConfig) - } catch (error) { - const axiosError = error as AxiosError - if (axiosError.response) { - return axiosError.response - } - throw error - } -} - -export const getResponseHeader = ( - response: AxiosResponse, - responseHeader?: string, -): string | undefined => { - if (responseHeader) { - const content = response.headers[responseHeader] - if (isString(content)) { - return content - } - } - return undefined -} + const controller = new AbortController(); + + let requestConfig: AxiosRequestConfig = { + data: body ?? formData, + headers, + method: options.method, + signal: controller.signal, + url, + withCredentials: config.WITH_CREDENTIALS, + }; + + onCancel(() => controller.abort()); + + for (const fn of config.interceptors.request._fns) { + requestConfig = await fn(requestConfig); + } + + try { + return await axiosClient.request(requestConfig); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + return axiosError.response; + } + throw error; + } +}; + +export const getResponseHeader = (response: AxiosResponse, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader]; + if (isString(content)) { + return content; + } + } + return undefined; +}; export const getResponseBody = (response: AxiosResponse): unknown => { - if (response.status !== 204) { - return response.data - } - return undefined -} - -export const catchErrorCodes = ( - options: ApiRequestOptions, - result: ApiResult, -): void => { - const errors: Record = { - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Payload Too Large", - 414: "URI Too Long", - 415: "Unsupported Media Type", - 416: "Range Not Satisfiable", - 417: "Expectation Failed", - 418: "Im a teapot", - 421: "Misdirected Request", - 422: "Unprocessable Content", - 423: "Locked", - 424: "Failed Dependency", - 425: "Too Early", - 426: "Upgrade Required", - 428: "Precondition Required", - 429: "Too Many Requests", - 431: "Request Header Fields Too Large", - 451: "Unavailable For Legal Reasons", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported", - 506: "Variant Also Negotiates", - 507: "Insufficient Storage", - 508: "Loop Detected", - 510: "Not Extended", - 511: "Network Authentication Required", - ...options.errors, - } - - const error = errors[result.status] - if (error) { - throw new ApiError(options, result, error) - } - - if (!result.ok) { - const errorStatus = result.status ?? "unknown" - const errorStatusText = result.statusText ?? "unknown" - const errorBody = (() => { - try { - return JSON.stringify(result.body, null, 2) - } catch (e) { - return undefined - } - })() - - throw new ApiError( - options, - result, - `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`, - ) - } -} + if (response.status !== 204) { + return response.data; + } + return undefined; +}; + +export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'Im a teapot', + 421: 'Misdirected Request', + 422: 'Unprocessable Content', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 510: 'Not Extended', + 511: 'Network Authentication Required', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? 'unknown'; + const errorStatusText = result.statusText ?? 'unknown'; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError(options, result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ); + } +}; /** * Request method @@ -329,59 +305,43 @@ export const catchErrorCodes = ( * @returns CancelablePromise * @throws ApiError */ -export const request = ( - config: OpenAPIConfig, - options: ApiRequestOptions, - axiosClient: AxiosInstance = axios, -): CancelablePromise => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options) - const formData = getFormData(options) - const body = getRequestBody(options) - const headers = await getHeaders(config, options) - - if (!onCancel.isCancelled) { - let response = await sendRequest( - config, - options, - url, - body, - formData, - headers, - onCancel, - axiosClient, - ) - - for (const fn of config.interceptors.response._fns) { - response = await fn(response) - } - - const responseBody = getResponseBody(response) - const responseHeader = getResponseHeader( - response, - options.responseHeader, - ) - - let transformedBody = responseBody - if (options.responseTransformer && isSuccess(response.status)) { - transformedBody = await options.responseTransformer(responseBody) - } - - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader ?? transformedBody, - } - - catchErrorCodes(options, result) - - resolve(result.body) - } - } catch (error) { - reject(error) - } - }) -} +export const request = (config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options); + + if (!onCancel.isCancelled) { + let response = await sendRequest(config, options, url, body, formData, headers, onCancel, axiosClient); + + for (const fn of config.interceptors.response._fns) { + response = await fn(response); + } + + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + let transformedBody = responseBody; + if (options.responseTransformer && isSuccess(response.status)) { + transformedBody = await options.responseTransformer(responseBody) + } + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? transformedBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; \ No newline at end of file diff --git a/frontend/src/client/index.ts b/frontend/src/client/index.ts index 2228dde8b4..50a1dd734c 100644 --- a/frontend/src/client/index.ts +++ b/frontend/src/client/index.ts @@ -1,6 +1,6 @@ // This file is auto-generated by @hey-api/openapi-ts -export { ApiError } from "./core/ApiError" -export { CancelablePromise, CancelError } from "./core/CancelablePromise" -export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI" -export * from "./sdk.gen" -export * from "./types.gen" +export { ApiError } from './core/ApiError'; +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI'; +export * from './sdk.gen'; +export * from './types.gen'; \ No newline at end of file diff --git a/frontend/src/client/sdk.gen.ts b/frontend/src/client/sdk.gen.ts index 92ded2bde8..63ead2497b 100644 --- a/frontend/src/client/sdk.gen.ts +++ b/frontend/src/client/sdk.gen.ts @@ -1,523 +1,585 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { CancelablePromise } from "./core/CancelablePromise" -import { OpenAPI } from "./core/OpenAPI" -import { request as __request } from "./core/request" -import type { - ItemsReadItemsData, - ItemsReadItemsResponse, - ItemsCreateItemData, - ItemsCreateItemResponse, - ItemsReadItemData, - ItemsReadItemResponse, - ItemsUpdateItemData, - ItemsUpdateItemResponse, - ItemsDeleteItemData, - ItemsDeleteItemResponse, - LoginLoginAccessTokenData, - LoginLoginAccessTokenResponse, - LoginTestTokenResponse, - LoginRecoverPasswordData, - LoginRecoverPasswordResponse, - LoginResetPasswordData, - LoginResetPasswordResponse, - LoginRecoverPasswordHtmlContentData, - LoginRecoverPasswordHtmlContentResponse, - UsersReadUsersData, - UsersReadUsersResponse, - UsersCreateUserData, - UsersCreateUserResponse, - UsersReadUserMeResponse, - UsersDeleteUserMeResponse, - UsersUpdateUserMeData, - UsersUpdateUserMeResponse, - UsersUpdatePasswordMeData, - UsersUpdatePasswordMeResponse, - UsersRegisterUserData, - UsersRegisterUserResponse, - UsersReadUserByIdData, - UsersReadUserByIdResponse, - UsersUpdateUserData, - UsersUpdateUserResponse, - UsersDeleteUserData, - UsersDeleteUserResponse, - UtilsTestEmailData, - UtilsTestEmailResponse, - UtilsHealthCheckResponse, -} from "./types.gen" +import type { CancelablePromise } from './core/CancelablePromise'; +import { OpenAPI } from './core/OpenAPI'; +import { request as __request } from './core/request'; +import type { ItemsReadItemsData, ItemsReadItemsResponse, ItemsCreateItemData, ItemsCreateItemResponse, ItemsReadItemData, ItemsReadItemResponse, ItemsUpdateItemData, ItemsUpdateItemResponse, ItemsDeleteItemData, ItemsDeleteItemResponse, LoginLoginAccessTokenData, LoginLoginAccessTokenResponse, LoginTestTokenResponse, LoginRecoverPasswordData, LoginRecoverPasswordResponse, LoginResetPasswordData, LoginResetPasswordResponse, LoginRecoverPasswordHtmlContentData, LoginRecoverPasswordHtmlContentResponse, PathsListPathsData, PathsListPathsResponse, PathsCreatePathData, PathsCreatePathResponse, PathsGetPathData, PathsGetPathResponse, PathsUpdatePathData, PathsUpdatePathResponse, PathsDeletePathData, PathsDeletePathResponse, PrivateCreateUserData, PrivateCreateUserResponse, UsersReadUsersData, UsersReadUsersResponse, UsersCreateUserData, UsersCreateUserResponse, UsersReadUserMeResponse, UsersDeleteUserMeResponse, UsersUpdateUserMeData, UsersUpdateUserMeResponse, UsersUpdatePasswordMeData, UsersUpdatePasswordMeResponse, UsersRegisterUserData, UsersRegisterUserResponse, UsersReadUserByIdData, UsersReadUserByIdResponse, UsersUpdateUserData, UsersUpdateUserResponse, UsersDeleteUserData, UsersDeleteUserResponse, UtilsTestEmailData, UtilsTestEmailResponse, UtilsHealthCheckResponse } from './types.gen'; export class ItemsService { - /** - * Read Items - * Retrieve items. - * @param data The data for the request. - * @param data.skip - * @param data.limit - * @returns ItemsPublic Successful Response - * @throws ApiError - */ - public static readItems( - data: ItemsReadItemsData = {}, - ): CancelablePromise { - return __request(OpenAPI, { - method: "GET", - url: "/api/v1/items/", - query: { - skip: data.skip, - limit: data.limit, - }, - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Create Item - * Create new item. - * @param data The data for the request. - * @param data.requestBody - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static createItem( - data: ItemsCreateItemData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/items/", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Read Item - * Get item by ID. - * @param data The data for the request. - * @param data.id - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static readItem( - data: ItemsReadItemData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "GET", - url: "/api/v1/items/{id}", - path: { - id: data.id, - }, - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Update Item - * Update an item. - * @param data The data for the request. - * @param data.id - * @param data.requestBody - * @returns ItemPublic Successful Response - * @throws ApiError - */ - public static updateItem( - data: ItemsUpdateItemData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "PUT", - url: "/api/v1/items/{id}", - path: { - id: data.id, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Delete Item - * Delete an item. - * @param data The data for the request. - * @param data.id - * @returns Message Successful Response - * @throws ApiError - */ - public static deleteItem( - data: ItemsDeleteItemData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "DELETE", - url: "/api/v1/items/{id}", - path: { - id: data.id, - }, - errors: { - 422: "Validation Error", - }, - }) - } + /** + * Read Items + * Retrieve items. + * @param data The data for the request. + * @param data.skip + * @param data.limit + * @returns ItemsPublic Successful Response + * @throws ApiError + */ + public static readItems(data: ItemsReadItemsData = {}): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/items/', + query: { + skip: data.skip, + limit: data.limit + }, + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Create Item + * Create new item. + * @param data The data for the request. + * @param data.requestBody + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static createItem(data: ItemsCreateItemData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/items/', + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Read Item + * Get item by ID. + * @param data The data for the request. + * @param data.id + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static readItem(data: ItemsReadItemData): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/items/{id}', + path: { + id: data.id + }, + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Update Item + * Update an item. + * @param data The data for the request. + * @param data.id + * @param data.requestBody + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static updateItem(data: ItemsUpdateItemData): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/v1/items/{id}', + path: { + id: data.id + }, + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Delete Item + * Delete an item. + * @param data The data for the request. + * @param data.id + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteItem(data: ItemsDeleteItemData): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/items/{id}', + path: { + id: data.id + }, + errors: { + 422: 'Validation Error' + } + }); + } + } export class LoginService { - /** - * Login Access Token - * OAuth2 compatible token login, get an access token for future requests - * @param data The data for the request. - * @param data.formData - * @returns Token Successful Response - * @throws ApiError - */ - public static loginAccessToken( - data: LoginLoginAccessTokenData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/login/access-token", - formData: data.formData, - mediaType: "application/x-www-form-urlencoded", - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Test Token - * Test access token - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static testToken(): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/login/test-token", - }) - } - - /** - * Recover Password - * Password Recovery - * @param data The data for the request. - * @param data.email - * @returns Message Successful Response - * @throws ApiError - */ - public static recoverPassword( - data: LoginRecoverPasswordData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/password-recovery/{email}", - path: { - email: data.email, - }, - errors: { - 422: "Validation Error", - }, - }) - } + /** + * Login Access Token + * OAuth2 compatible token login, get an access token for future requests + * @param data The data for the request. + * @param data.formData + * @returns Token Successful Response + * @throws ApiError + */ + public static loginAccessToken(data: LoginLoginAccessTokenData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/login/access-token', + formData: data.formData, + mediaType: 'application/x-www-form-urlencoded', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Test Token + * Test access token + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static testToken(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/login/test-token' + }); + } + + /** + * Recover Password + * Password Recovery + * @param data The data for the request. + * @param data.email + * @returns Message Successful Response + * @throws ApiError + */ + public static recoverPassword(data: LoginRecoverPasswordData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/password-recovery/{email}', + path: { + email: data.email + }, + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Reset Password + * Reset password + * @param data The data for the request. + * @param data.requestBody + * @returns Message Successful Response + * @throws ApiError + */ + public static resetPassword(data: LoginResetPasswordData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/reset-password/', + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Recover Password Html Content + * HTML Content for Password Recovery + * @param data The data for the request. + * @param data.email + * @returns string Successful Response + * @throws ApiError + */ + public static recoverPasswordHtmlContent(data: LoginRecoverPasswordHtmlContentData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/password-recovery-html-content/{email}', + path: { + email: data.email + }, + errors: { + 422: 'Validation Error' + } + }); + } + +} - /** - * Reset Password - * Reset password - * @param data The data for the request. - * @param data.requestBody - * @returns Message Successful Response - * @throws ApiError - */ - public static resetPassword( - data: LoginResetPasswordData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/reset-password/", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }) - } +export class PathsService { + /** + * List Paths + * Retrieve paths with their steps. + * @param data The data for the request. + * @param data.skip + * @param data.limit + * @returns PathResponse Successful Response + * @throws ApiError + */ + public static listPaths(data: PathsListPathsData = {}): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/paths/', + query: { + skip: data.skip, + limit: data.limit + }, + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Create Path + * Create a new path with its steps. + * @param data The data for the request. + * @param data.requestBody + * @returns PathResponse Successful Response + * @throws ApiError + */ + public static createPath(data: PathsCreatePathData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/paths/', + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Get Path + * Get path by ID with all its steps. + * @param data The data for the request. + * @param data.pathId + * @returns PathResponse Successful Response + * @throws ApiError + */ + public static getPath(data: PathsGetPathData): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/paths/{path_id}', + path: { + path_id: data.pathId + }, + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Update Path + * Update a path and all its steps. Steps will be completely replaced. + * @param data The data for the request. + * @param data.pathId + * @param data.requestBody + * @returns PathResponse Successful Response + * @throws ApiError + */ + public static updatePath(data: PathsUpdatePathData): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/v1/paths/{path_id}', + path: { + path_id: data.pathId + }, + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Delete Path + * Delete a path and all its steps. + * @param data The data for the request. + * @param data.pathId + * @returns unknown Successful Response + * @throws ApiError + */ + public static deletePath(data: PathsDeletePathData): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/paths/{path_id}', + path: { + path_id: data.pathId + }, + errors: { + 422: 'Validation Error' + } + }); + } + +} - /** - * Recover Password Html Content - * HTML Content for Password Recovery - * @param data The data for the request. - * @param data.email - * @returns string Successful Response - * @throws ApiError - */ - public static recoverPasswordHtmlContent( - data: LoginRecoverPasswordHtmlContentData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/password-recovery-html-content/{email}", - path: { - email: data.email, - }, - errors: { - 422: "Validation Error", - }, - }) - } +export class PrivateService { + /** + * Create User + * Create a new user. + * @param data The data for the request. + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static createUser(data: PrivateCreateUserData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/private/users/', + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + } export class UsersService { - /** - * Read Users - * Retrieve users. - * @param data The data for the request. - * @param data.skip - * @param data.limit - * @returns UsersPublic Successful Response - * @throws ApiError - */ - public static readUsers( - data: UsersReadUsersData = {}, - ): CancelablePromise { - return __request(OpenAPI, { - method: "GET", - url: "/api/v1/users/", - query: { - skip: data.skip, - limit: data.limit, - }, - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Create User - * Create new user. - * @param data The data for the request. - * @param data.requestBody - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static createUser( - data: UsersCreateUserData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/users/", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Read User Me - * Get current user. - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static readUserMe(): CancelablePromise { - return __request(OpenAPI, { - method: "GET", - url: "/api/v1/users/me", - }) - } - - /** - * Delete User Me - * Delete own user. - * @returns Message Successful Response - * @throws ApiError - */ - public static deleteUserMe(): CancelablePromise { - return __request(OpenAPI, { - method: "DELETE", - url: "/api/v1/users/me", - }) - } - - /** - * Update User Me - * Update own user. - * @param data The data for the request. - * @param data.requestBody - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static updateUserMe( - data: UsersUpdateUserMeData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "PATCH", - url: "/api/v1/users/me", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Update Password Me - * Update own password. - * @param data The data for the request. - * @param data.requestBody - * @returns Message Successful Response - * @throws ApiError - */ - public static updatePasswordMe( - data: UsersUpdatePasswordMeData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "PATCH", - url: "/api/v1/users/me/password", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Register User - * Create new user without the need to be logged in. - * @param data The data for the request. - * @param data.requestBody - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static registerUser( - data: UsersRegisterUserData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/users/signup", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Read User By Id - * Get a specific user by id. - * @param data The data for the request. - * @param data.userId - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static readUserById( - data: UsersReadUserByIdData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "GET", - url: "/api/v1/users/{user_id}", - path: { - user_id: data.userId, - }, - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Update User - * Update a user. - * @param data The data for the request. - * @param data.userId - * @param data.requestBody - * @returns UserPublic Successful Response - * @throws ApiError - */ - public static updateUser( - data: UsersUpdateUserData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "PATCH", - url: "/api/v1/users/{user_id}", - path: { - user_id: data.userId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Delete User - * Delete a user. - * @param data The data for the request. - * @param data.userId - * @returns Message Successful Response - * @throws ApiError - */ - public static deleteUser( - data: UsersDeleteUserData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "DELETE", - url: "/api/v1/users/{user_id}", - path: { - user_id: data.userId, - }, - errors: { - 422: "Validation Error", - }, - }) - } + /** + * Read Users + * Retrieve users. + * @param data The data for the request. + * @param data.skip + * @param data.limit + * @returns UsersPublic Successful Response + * @throws ApiError + */ + public static readUsers(data: UsersReadUsersData = {}): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/users/', + query: { + skip: data.skip, + limit: data.limit + }, + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Create User + * Create new user. + * @param data The data for the request. + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static createUser(data: UsersCreateUserData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/users/', + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Read User Me + * Get current user. + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static readUserMe(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/users/me' + }); + } + + /** + * Delete User Me + * Delete own user. + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteUserMe(): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/users/me' + }); + } + + /** + * Update User Me + * Update own user. + * @param data The data for the request. + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static updateUserMe(data: UsersUpdateUserMeData): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/v1/users/me', + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Update Password Me + * Update own password. + * @param data The data for the request. + * @param data.requestBody + * @returns Message Successful Response + * @throws ApiError + */ + public static updatePasswordMe(data: UsersUpdatePasswordMeData): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/v1/users/me/password', + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Register User + * Create new user without the need to be logged in. + * @param data The data for the request. + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static registerUser(data: UsersRegisterUserData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/users/signup', + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Read User By Id + * Get a specific user by id. + * @param data The data for the request. + * @param data.userId + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static readUserById(data: UsersReadUserByIdData): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/users/{user_id}', + path: { + user_id: data.userId + }, + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Update User + * Update a user. + * @param data The data for the request. + * @param data.userId + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static updateUser(data: UsersUpdateUserData): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/v1/users/{user_id}', + path: { + user_id: data.userId + }, + body: data.requestBody, + mediaType: 'application/json', + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Delete User + * Delete a user. + * @param data The data for the request. + * @param data.userId + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteUser(data: UsersDeleteUserData): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/v1/users/{user_id}', + path: { + user_id: data.userId + }, + errors: { + 422: 'Validation Error' + } + }); + } + } export class UtilsService { - /** - * Test Email - * Test emails. - * @param data The data for the request. - * @param data.emailTo - * @returns Message Successful Response - * @throws ApiError - */ - public static testEmail( - data: UtilsTestEmailData, - ): CancelablePromise { - return __request(OpenAPI, { - method: "POST", - url: "/api/v1/utils/test-email/", - query: { - email_to: data.emailTo, - }, - errors: { - 422: "Validation Error", - }, - }) - } - - /** - * Health Check - * @returns boolean Successful Response - * @throws ApiError - */ - public static healthCheck(): CancelablePromise { - return __request(OpenAPI, { - method: "GET", - url: "/api/v1/utils/health-check/", - }) - } -} + /** + * Test Email + * Test emails. + * @param data The data for the request. + * @param data.emailTo + * @returns Message Successful Response + * @throws ApiError + */ + public static testEmail(data: UtilsTestEmailData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/utils/test-email/', + query: { + email_to: data.emailTo + }, + errors: { + 422: 'Validation Error' + } + }); + } + + /** + * Health Check + * @returns boolean Successful Response + * @throws ApiError + */ + public static healthCheck(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/utils/health-check/' + }); + } + +} \ No newline at end of file diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index c2a58d06cb..457eed18bb 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -1,221 +1,302 @@ // This file is auto-generated by @hey-api/openapi-ts export type Body_login_login_access_token = { - grant_type?: string | null - username: string - password: string - scope?: string - client_id?: string | null - client_secret?: string | null -} + grant_type?: (string | null); + username: string; + password: string; + scope?: string; + client_id?: (string | null); + client_secret?: (string | null); +}; export type HTTPValidationError = { - detail?: Array -} + detail?: Array; +}; export type ItemCreate = { - title: string - description?: string | null -} + title: string; + description?: (string | null); +}; export type ItemPublic = { - title: string - description?: string | null - id: string - owner_id: string -} + title: string; + description?: (string | null); + id: string; + owner_id: string; +}; export type ItemsPublic = { - data: Array - count: number -} + data: Array; + count: number; +}; export type ItemUpdate = { - title?: string | null - description?: string | null -} + title?: (string | null); + description?: (string | null); +}; export type Message = { - message: string -} + message: string; +}; export type NewPassword = { - token: string - new_password: string -} + token: string; + new_password: string; +}; + +export type PathCreate = { + title: string; + path_summary?: (string | null); + steps: Array; +}; + +export type PathResponse = { + id: string; + title: string; + path_summary: (string | null); + steps: Array; +}; + +export type PrivateUserCreate = { + email: string; + password: string; + full_name: string; + is_verified?: boolean; +}; + +export type StepCreate = { + number: number; + role_prompt?: (string | null); + validation_prompt?: (string | null); + exposition?: (YoutubeExposition | null); +}; + +export type StepResponse = { + id: number; + number: number; + role_prompt?: (string | null); + validation_prompt?: (string | null); + exposition?: (YoutubeExposition | null); +}; export type Token = { - access_token: string - token_type?: string -} + access_token: string; + token_type?: string; +}; export type UpdatePassword = { - current_password: string - new_password: string -} + current_password: string; + new_password: string; +}; export type UserCreate = { - email: string - is_active?: boolean - is_superuser?: boolean - full_name?: string | null - password: string -} + email: string; + is_active?: boolean; + is_superuser?: boolean; + full_name?: (string | null); + password: string; +}; export type UserPublic = { - email: string - is_active?: boolean - is_superuser?: boolean - full_name?: string | null - id: string -} + email: string; + is_active?: boolean; + is_superuser?: boolean; + full_name?: (string | null); + id: string; +}; export type UserRegister = { - email: string - password: string - full_name?: string | null -} + email: string; + password: string; + full_name?: (string | null); +}; export type UsersPublic = { - data: Array - count: number -} + data: Array; + count: number; +}; export type UserUpdate = { - email?: string | null - is_active?: boolean - is_superuser?: boolean - full_name?: string | null - password?: string | null -} + email?: (string | null); + is_active?: boolean; + is_superuser?: boolean; + full_name?: (string | null); + password?: (string | null); +}; export type UserUpdateMe = { - full_name?: string | null - email?: string | null -} + full_name?: (string | null); + email?: (string | null); +}; export type ValidationError = { - loc: Array - msg: string - type: string -} + loc: Array<(string | number)>; + msg: string; + type: string; +}; + +export type YoutubeExposition = { + url: string; + start_time?: (number | null); + end_time?: (number | null); +}; export type ItemsReadItemsData = { - limit?: number - skip?: number -} + limit?: number; + skip?: number; +}; -export type ItemsReadItemsResponse = ItemsPublic +export type ItemsReadItemsResponse = (ItemsPublic); export type ItemsCreateItemData = { - requestBody: ItemCreate -} + requestBody: ItemCreate; +}; -export type ItemsCreateItemResponse = ItemPublic +export type ItemsCreateItemResponse = (ItemPublic); export type ItemsReadItemData = { - id: string -} + id: string; +}; -export type ItemsReadItemResponse = ItemPublic +export type ItemsReadItemResponse = (ItemPublic); export type ItemsUpdateItemData = { - id: string - requestBody: ItemUpdate -} + id: string; + requestBody: ItemUpdate; +}; -export type ItemsUpdateItemResponse = ItemPublic +export type ItemsUpdateItemResponse = (ItemPublic); export type ItemsDeleteItemData = { - id: string -} + id: string; +}; -export type ItemsDeleteItemResponse = Message +export type ItemsDeleteItemResponse = (Message); export type LoginLoginAccessTokenData = { - formData: Body_login_login_access_token -} + formData: Body_login_login_access_token; +}; -export type LoginLoginAccessTokenResponse = Token +export type LoginLoginAccessTokenResponse = (Token); -export type LoginTestTokenResponse = UserPublic +export type LoginTestTokenResponse = (UserPublic); export type LoginRecoverPasswordData = { - email: string -} + email: string; +}; -export type LoginRecoverPasswordResponse = Message +export type LoginRecoverPasswordResponse = (Message); export type LoginResetPasswordData = { - requestBody: NewPassword -} + requestBody: NewPassword; +}; -export type LoginResetPasswordResponse = Message +export type LoginResetPasswordResponse = (Message); export type LoginRecoverPasswordHtmlContentData = { - email: string -} + email: string; +}; + +export type LoginRecoverPasswordHtmlContentResponse = (string); + +export type PathsListPathsData = { + limit?: number; + skip?: number; +}; + +export type PathsListPathsResponse = (Array); + +export type PathsCreatePathData = { + requestBody: PathCreate; +}; + +export type PathsCreatePathResponse = (PathResponse); + +export type PathsGetPathData = { + pathId: string; +}; + +export type PathsGetPathResponse = (PathResponse); + +export type PathsUpdatePathData = { + pathId: string; + requestBody: PathCreate; +}; + +export type PathsUpdatePathResponse = (PathResponse); + +export type PathsDeletePathData = { + pathId: string; +}; + +export type PathsDeletePathResponse = ({ + [key: string]: unknown; +}); + +export type PrivateCreateUserData = { + requestBody: PrivateUserCreate; +}; -export type LoginRecoverPasswordHtmlContentResponse = string +export type PrivateCreateUserResponse = (UserPublic); export type UsersReadUsersData = { - limit?: number - skip?: number -} + limit?: number; + skip?: number; +}; -export type UsersReadUsersResponse = UsersPublic +export type UsersReadUsersResponse = (UsersPublic); export type UsersCreateUserData = { - requestBody: UserCreate -} + requestBody: UserCreate; +}; -export type UsersCreateUserResponse = UserPublic +export type UsersCreateUserResponse = (UserPublic); -export type UsersReadUserMeResponse = UserPublic +export type UsersReadUserMeResponse = (UserPublic); -export type UsersDeleteUserMeResponse = Message +export type UsersDeleteUserMeResponse = (Message); export type UsersUpdateUserMeData = { - requestBody: UserUpdateMe -} + requestBody: UserUpdateMe; +}; -export type UsersUpdateUserMeResponse = UserPublic +export type UsersUpdateUserMeResponse = (UserPublic); export type UsersUpdatePasswordMeData = { - requestBody: UpdatePassword -} + requestBody: UpdatePassword; +}; -export type UsersUpdatePasswordMeResponse = Message +export type UsersUpdatePasswordMeResponse = (Message); export type UsersRegisterUserData = { - requestBody: UserRegister -} + requestBody: UserRegister; +}; -export type UsersRegisterUserResponse = UserPublic +export type UsersRegisterUserResponse = (UserPublic); export type UsersReadUserByIdData = { - userId: string -} + userId: string; +}; -export type UsersReadUserByIdResponse = UserPublic +export type UsersReadUserByIdResponse = (UserPublic); export type UsersUpdateUserData = { - requestBody: UserUpdate - userId: string -} + requestBody: UserUpdate; + userId: string; +}; -export type UsersUpdateUserResponse = UserPublic +export type UsersUpdateUserResponse = (UserPublic); export type UsersDeleteUserData = { - userId: string -} + userId: string; +}; -export type UsersDeleteUserResponse = Message +export type UsersDeleteUserResponse = (Message); export type UtilsTestEmailData = { - emailTo: string -} + emailTo: string; +}; -export type UtilsTestEmailResponse = Message +export type UtilsTestEmailResponse = (Message); -export type UtilsHealthCheckResponse = boolean +export type UtilsHealthCheckResponse = (boolean); \ No newline at end of file diff --git a/frontend/src/components/Common/SidebarItems.tsx b/frontend/src/components/Common/SidebarItems.tsx index 929e8f785e..6841a4ec6c 100644 --- a/frontend/src/components/Common/SidebarItems.tsx +++ b/frontend/src/components/Common/SidebarItems.tsx @@ -1,13 +1,14 @@ import { Box, Flex, Icon, Text, useColorModeValue } from "@chakra-ui/react" import { useQueryClient } from "@tanstack/react-query" import { Link } from "@tanstack/react-router" -import { FiBriefcase, FiHome, FiSettings, FiUsers } from "react-icons/fi" +import { FiBriefcase, FiGitBranch, FiHome, FiSettings, FiUsers } from "react-icons/fi" import type { UserPublic } from "../../client" const items = [ { icon: FiHome, title: "Dashboard", path: "/" }, { icon: FiBriefcase, title: "Items", path: "/items" }, + { icon: FiGitBranch, title: "Learning Paths", path: "/paths" }, { icon: FiSettings, title: "User Settings", path: "/settings" }, ] diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 0e78c9ba20..7fa9279105 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -16,6 +16,7 @@ import { Route as ResetPasswordImport } from './routes/reset-password' import { Route as RecoverPasswordImport } from './routes/recover-password' import { Route as LoginImport } from './routes/login' import { Route as LayoutImport } from './routes/_layout' +import { Route as LayoutPathsImport } from './routes/_layout/paths' import { Route as LayoutIndexImport } from './routes/_layout/index' import { Route as LayoutSettingsImport } from './routes/_layout/settings' import { Route as LayoutItemsImport } from './routes/_layout/items' @@ -48,6 +49,11 @@ const LayoutRoute = LayoutImport.update({ getParentRoute: () => rootRoute, } as any) +const LayoutPathsRoute = LayoutPathsImport.update({ + path: '/paths', + getParentRoute: () => LayoutRoute, +} as any) + const LayoutIndexRoute = LayoutIndexImport.update({ path: '/', getParentRoute: () => LayoutRoute, @@ -108,6 +114,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutIndexImport parentRoute: typeof LayoutImport } + '/_layout/paths': { + preLoaderRoute: typeof LayoutPathsImport + parentRoute: typeof LayoutImport + } } } @@ -119,6 +129,7 @@ export const routeTree = rootRoute.addChildren([ LayoutItemsRoute, LayoutSettingsRoute, LayoutIndexRoute, + LayoutPathsRoute, ]), LoginRoute, RecoverPasswordRoute, diff --git a/frontend/src/routes/_layout/paths.tsx b/frontend/src/routes/_layout/paths.tsx new file mode 100644 index 0000000000..997063c004 --- /dev/null +++ b/frontend/src/routes/_layout/paths.tsx @@ -0,0 +1,143 @@ +import React from "react" +import { + Container, + Heading, + SkeletonText, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, +} from "@chakra-ui/react" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { createFileRoute, useNavigate } from "@tanstack/react-router" +import { useEffect } from "react" +import { z } from "zod" + +import { PathsService } from "../../client" +import ActionsMenu from "../../components/Common/ActionsMenu" +import Navbar from "../../components/Common/Navbar" +import { PaginationFooter } from "../../components/Common/PaginationFooter" + +const pathsSearchSchema = z.object({ + page: z.number().catch(1), +}) + +export const Route = createFileRoute('/_layout/paths')({ + component: Paths, + validateSearch: (search) => pathsSearchSchema.parse(search), +}) + +const PER_PAGE = 5 + +function getPathsQueryOptions({ page }: { page: number }) { + return { + queryFn: () => + PathsService.listPaths({ skip: (page - 1) * PER_PAGE, limit: PER_PAGE }), + queryKey: ["paths", { page }], + } +} + +function PathsTable() { + const queryClient = useQueryClient() + const { page } = Route.useSearch() + const navigate = useNavigate({ from: Route.fullPath }) + const setPage = (page: number) => + navigate({ + to: "/paths", + search: (prev) => ({ ...prev, page }) + }) + + const { + data: paths, + isPending, + isPlaceholderData, + } = useQuery({ + ...getPathsQueryOptions({ page }), + placeholderData: (prevData) => prevData, + }) + + const hasNextPage = !isPlaceholderData && paths?.data?.length === PER_PAGE + const hasPreviousPage = page > 1 + + useEffect(() => { + if (hasNextPage) { + queryClient.prefetchQuery(getPathsQueryOptions({ page: page + 1 })) + } + }, [page, queryClient, hasNextPage]) + + return ( + <> + + + + + + + + + + + {isPending ? ( + + + {new Array(4).fill(null).map((_, index) => ( + + ))} + + + ) : ( + + {paths?.data?.map((path) => ( + + + + + + + ))} + + )} +
TitleSummaryStepsActions
+ +
+ {path.title} + + {path.path_summary || "N/A"} + {path.steps?.length || 0} + navigate({ to: `/paths/${path.id}/edit` })} + onView={() => navigate({ to: `/paths/${path.id}` })} + /> +
+
+ + + ) +} + +function Paths() { + return ( + + + Learning Paths + + + null} /> + + + ) +} diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000000..b195e51ceb --- /dev/null +++ b/schema.sql @@ -0,0 +1,213 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 12.22 (Debian 12.22-1.pgdg120+1) +-- Dumped by pg_dump version 12.22 (Debian 12.22-1.pgdg120+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; + + +-- +-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)'; + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: alembic_version; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.alembic_version ( + version_num character varying(32) NOT NULL +); + + +ALTER TABLE public.alembic_version OWNER TO postgres; + +-- +-- Name: item; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.item ( + description character varying(255), + title character varying(255) NOT NULL, + id uuid NOT NULL, + owner_id uuid NOT NULL +); + + +ALTER TABLE public.item OWNER TO postgres; + +-- +-- Name: path; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.path ( + id uuid NOT NULL, + creator_id uuid NOT NULL, + title character varying(255) NOT NULL, + path_summary character varying(255), + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +ALTER TABLE public.path OWNER TO postgres; + +-- +-- Name: step; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.step ( + id integer NOT NULL, + path_id uuid NOT NULL, + number integer NOT NULL, + role_prompt character varying, + validation_prompt character varying, + exposition_json character varying +); + + +ALTER TABLE public.step OWNER TO postgres; + +-- +-- Name: step_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.step_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.step_id_seq OWNER TO postgres; + +-- +-- Name: step_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.step_id_seq OWNED BY public.step.id; + + +-- +-- Name: user; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public."user" ( + email character varying(255) NOT NULL, + is_active boolean NOT NULL, + is_superuser boolean NOT NULL, + full_name character varying(255), + hashed_password character varying NOT NULL, + id uuid NOT NULL +); + + +ALTER TABLE public."user" OWNER TO postgres; + +-- +-- Name: step id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.step ALTER COLUMN id SET DEFAULT nextval('public.step_id_seq'::regclass); + + +-- +-- Name: alembic_version alembic_version_pkc; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.alembic_version + ADD CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num); + + +-- +-- Name: item item_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.item + ADD CONSTRAINT item_pkey PRIMARY KEY (id); + + +-- +-- Name: path path_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.path + ADD CONSTRAINT path_pkey PRIMARY KEY (id); + + +-- +-- Name: step step_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.step + ADD CONSTRAINT step_pkey PRIMARY KEY (id); + + +-- +-- Name: user user_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public."user" + ADD CONSTRAINT user_pkey PRIMARY KEY (id); + + +-- +-- Name: ix_user_email; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX ix_user_email ON public."user" USING btree (email); + + +-- +-- Name: item item_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.item + ADD CONSTRAINT item_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: path path_creator_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.path + ADD CONSTRAINT path_creator_id_fkey FOREIGN KEY (creator_id) REFERENCES public."user"(id); + + +-- +-- Name: step step_path_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.step + ADD CONSTRAINT step_path_id_fkey FOREIGN KEY (path_id) REFERENCES public.path(id); + + +-- +-- PostgreSQL database dump complete +-- + From 7db0b0409c896eef60bff8a72c0279567ff23e37 Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Fri, 31 Jan 2025 17:33:39 +0100 Subject: [PATCH 747/771] last alembic migration fixed --- .../alembic/versions/ea8d60701d49_add_path_and_step_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py b/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py index cbddfdf2e2..26bfe7c32a 100644 --- a/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py +++ b/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py @@ -1,7 +1,7 @@ """add path and step models Revision ID: ea8d60701d49 -Revises: e2412789c190 +Revises: 1a31ce608336 Create Date: 2025-01-31 17:08:00.000000 """ @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision: str = 'ea8d60701d49' -down_revision: Union[str, None] = 'e2412789c190' +down_revision: Union[str, None] = '1a31ce608336' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None From 61932f759ae14ffeac7b148a170e0554ad685ab1 Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Fri, 31 Jan 2025 17:39:08 +0100 Subject: [PATCH 748/771] Revert "last alembic migration fixed" This reverts commit 60b797137f95777cc3e249783f9ce9f29d1f2eb3. --- .../alembic/versions/ea8d60701d49_add_path_and_step_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py b/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py index 26bfe7c32a..cbddfdf2e2 100644 --- a/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py +++ b/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py @@ -1,7 +1,7 @@ """add path and step models Revision ID: ea8d60701d49 -Revises: 1a31ce608336 +Revises: e2412789c190 Create Date: 2025-01-31 17:08:00.000000 """ @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision: str = 'ea8d60701d49' -down_revision: Union[str, None] = '1a31ce608336' +down_revision: Union[str, None] = 'e2412789c190' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None From ef89c8a29188072f1b8b118fabb5dd136eb04d9b Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Fri, 31 Jan 2025 17:40:21 +0100 Subject: [PATCH 749/771] Add path and step models migration with correct parent revision -Connect mail setup -Integrate paths and steps into backend (complete) -Integrate paths and steps into the frontend (incomplete) --- .../ea8d60701d49_add_path_and_step_models.py | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py b/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py index cbddfdf2e2..ac1d4fdafc 100644 --- a/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py +++ b/backend/app/alembic/versions/ea8d60701d49_add_path_and_step_models.py @@ -1,54 +1,48 @@ """add path and step models Revision ID: ea8d60701d49 -Revises: e2412789c190 -Create Date: 2025-01-31 17:08:00.000000 +Revises: 1a31ce608336 +Create Date: 2025-01-31 17:39:00.000000 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa -import sqlmodel +import sqlmodel.sql.sqltypes # revision identifiers, used by Alembic. revision: str = 'ea8d60701d49' -down_revision: Union[str, None] = 'e2412789c190' +down_revision: Union[str, None] = '1a31ce608336' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - # Create path table - op.create_table( - 'path', - sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False), - sa.Column('creator_id', sqlmodel.sql.sqltypes.GUID(), nullable=False), - sa.Column('title', sa.String(length=255), nullable=False), - sa.Column('path_summary', sa.String(length=255), nullable=True), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint(['creator_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('path', + sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column('title', sqlmodel.sql.sqltypes.AutoString(length=100), nullable=False), + sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('owner_id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') ) - - # Create step table and sequence - op.create_sequence('step_id_seq') - op.create_table( - 'step', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('path_id', sqlmodel.sql.sqltypes.GUID(), nullable=False), - sa.Column('number', sa.Integer(), nullable=False), - sa.Column('role_prompt', sa.String(), nullable=True), - sa.Column('validation_prompt', sa.String(), nullable=True), - sa.Column('exposition_json', sa.String(), nullable=True), - sa.ForeignKeyConstraint(['path_id'], ['path.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table('step', + sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column('title', sqlmodel.sql.sqltypes.AutoString(length=100), nullable=False), + sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('path_id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column('order', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['path_id'], ['path.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') ) + # ### end Alembic commands ### def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### op.drop_table('step') - op.drop_sequence('step_id_seq') op.drop_table('path') + # ### end Alembic commands ### From 7b60b040dbb44d33b6a0a219c17d8c1d6bb22d3c Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Fri, 31 Jan 2025 18:28:18 +0100 Subject: [PATCH 750/771] before implementing nested routing --- frontend/src/client/sdk.gen.ts | 10 +++---- frontend/src/client/types.gen.ts | 30 ++++++++++++------- .../src/components/Common/ActionsMenu.tsx | 4 +-- frontend/src/routes/_layout/paths.tsx | 7 ++--- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/frontend/src/client/sdk.gen.ts b/frontend/src/client/sdk.gen.ts index 63ead2497b..abdc12cccf 100644 --- a/frontend/src/client/sdk.gen.ts +++ b/frontend/src/client/sdk.gen.ts @@ -218,11 +218,11 @@ export class LoginService { export class PathsService { /** * List Paths - * Retrieve paths with their steps. + * Retrieve paths without loading steps. * @param data The data for the request. * @param data.skip * @param data.limit - * @returns PathResponse Successful Response + * @returns PathsPublic Successful Response * @throws ApiError */ public static listPaths(data: PathsListPathsData = {}): CancelablePromise { @@ -244,7 +244,7 @@ export class PathsService { * Create a new path with its steps. * @param data The data for the request. * @param data.requestBody - * @returns PathResponse Successful Response + * @returns PathPublic Successful Response * @throws ApiError */ public static createPath(data: PathsCreatePathData): CancelablePromise { @@ -264,7 +264,7 @@ export class PathsService { * Get path by ID with all its steps. * @param data The data for the request. * @param data.pathId - * @returns PathResponse Successful Response + * @returns PathPublic Successful Response * @throws ApiError */ public static getPath(data: PathsGetPathData): CancelablePromise { @@ -286,7 +286,7 @@ export class PathsService { * @param data The data for the request. * @param data.pathId * @param data.requestBody - * @returns PathResponse Successful Response + * @returns PathPublic Successful Response * @throws ApiError */ public static updatePath(data: PathsUpdatePathData): CancelablePromise { diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index 457eed18bb..784e70fdc2 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -50,11 +50,23 @@ export type PathCreate = { steps: Array; }; -export type PathResponse = { +export type PathInList = { id: string; title: string; path_summary: (string | null); - steps: Array; + created_at: string; +}; + +export type PathPublic = { + id: string; + title: string; + path_summary: (string | null); + steps: Array; +}; + +export type PathsPublic = { + data: Array; + count: number; }; export type PrivateUserCreate = { @@ -71,7 +83,7 @@ export type StepCreate = { exposition?: (YoutubeExposition | null); }; -export type StepResponse = { +export type StepPublic = { id: number; number: number; role_prompt?: (string | null); @@ -204,34 +216,32 @@ export type PathsListPathsData = { skip?: number; }; -export type PathsListPathsResponse = (Array); +export type PathsListPathsResponse = (PathsPublic); export type PathsCreatePathData = { requestBody: PathCreate; }; -export type PathsCreatePathResponse = (PathResponse); +export type PathsCreatePathResponse = (PathPublic); export type PathsGetPathData = { pathId: string; }; -export type PathsGetPathResponse = (PathResponse); +export type PathsGetPathResponse = (PathPublic); export type PathsUpdatePathData = { pathId: string; requestBody: PathCreate; }; -export type PathsUpdatePathResponse = (PathResponse); +export type PathsUpdatePathResponse = (PathPublic); export type PathsDeletePathData = { pathId: string; }; -export type PathsDeletePathResponse = ({ - [key: string]: unknown; -}); +export type PathsDeletePathResponse = (unknown); export type PrivateCreateUserData = { requestBody: PrivateUserCreate; diff --git a/frontend/src/components/Common/ActionsMenu.tsx b/frontend/src/components/Common/ActionsMenu.tsx index 4ff94ee3ea..de06125482 100644 --- a/frontend/src/components/Common/ActionsMenu.tsx +++ b/frontend/src/components/Common/ActionsMenu.tsx @@ -9,14 +9,14 @@ import { import { BsThreeDotsVertical } from "react-icons/bs" import { FiEdit, FiTrash } from "react-icons/fi" -import type { ItemPublic, UserPublic } from "../../client" +import type { ItemPublic, UserPublic, PathInList } from "../../client" import EditUser from "../Admin/EditUser" import EditItem from "../Items/EditItem" import Delete from "./DeleteAlert" interface ActionsMenuProps { type: string - value: ItemPublic | UserPublic + value: ItemPublic | UserPublic | PathInList disabled?: boolean } diff --git a/frontend/src/routes/_layout/paths.tsx b/frontend/src/routes/_layout/paths.tsx index 997063c004..e1e9539482 100644 --- a/frontend/src/routes/_layout/paths.tsx +++ b/frontend/src/routes/_layout/paths.tsx @@ -1,4 +1,3 @@ -import React from "react" import { Container, Heading, @@ -47,7 +46,7 @@ function PathsTable() { const setPage = (page: number) => navigate({ to: "/paths", - search: (prev) => ({ ...prev, page }) + search: (prev: {[key: string]: string}) => ({ ...prev, page }) }) const { @@ -76,14 +75,13 @@ function PathsTable() { Title Summary - Steps Actions {isPending ? ( - {new Array(4).fill(null).map((_, index) => ( + {new Array(3).fill(null).map((_, index) => ( @@ -104,7 +102,6 @@ function PathsTable() { > {path.path_summary || "N/A"} - {path.steps?.length || 0} Date: Fri, 31 Jan 2025 20:22:08 +0100 Subject: [PATCH 751/771] progress in implementing the paths into the template --- .../src/components/Paths/AddPathButton.tsx | 18 +++++++++++++ frontend/src/routes/_layout/paths.tsx | 5 ++-- frontend/src/routes/paths/$pathId/index.tsx | 26 +++++++++++++++++++ frontend/src/routes/paths/create/index.tsx | 19 ++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/Paths/AddPathButton.tsx create mode 100644 frontend/src/routes/paths/$pathId/index.tsx create mode 100644 frontend/src/routes/paths/create/index.tsx diff --git a/frontend/src/components/Paths/AddPathButton.tsx b/frontend/src/components/Paths/AddPathButton.tsx new file mode 100644 index 0000000000..16aecfe5a6 --- /dev/null +++ b/frontend/src/components/Paths/AddPathButton.tsx @@ -0,0 +1,18 @@ +import { useNavigate } from "@tanstack/react-router" + +interface AddPathButtonProps { + isOpen: boolean + onClose: () => void +} + +export default function AddPathButton({ isOpen, onClose }: AddPathButtonProps) { + const navigate = useNavigate() + + // Redirect to create page when "opened" + if (isOpen) { + navigate({ to: "/paths/create" }) + onClose() + } + + return null +} diff --git a/frontend/src/routes/_layout/paths.tsx b/frontend/src/routes/_layout/paths.tsx index e1e9539482..dd44a87b7b 100644 --- a/frontend/src/routes/_layout/paths.tsx +++ b/frontend/src/routes/_layout/paths.tsx @@ -17,6 +17,7 @@ import { z } from "zod" import { PathsService } from "../../client" import ActionsMenu from "../../components/Common/ActionsMenu" +import AddPathButton from "../../components/Paths/AddPathButton" import Navbar from "../../components/Common/Navbar" import { PaginationFooter } from "../../components/Common/PaginationFooter" @@ -106,8 +107,6 @@ function PathsTable() { navigate({ to: `/paths/${path.id}/edit` })} - onView={() => navigate({ to: `/paths/${path.id}` })} /> @@ -133,7 +132,7 @@ function Paths() { Learning Paths - null} /> + ) diff --git a/frontend/src/routes/paths/$pathId/index.tsx b/frontend/src/routes/paths/$pathId/index.tsx new file mode 100644 index 0000000000..1a73d4a201 --- /dev/null +++ b/frontend/src/routes/paths/$pathId/index.tsx @@ -0,0 +1,26 @@ +import { Container, Heading } from "@chakra-ui/react" +import { createFileRoute } from "@tanstack/react-router" +import { z } from "zod" +import React from "react" + +// Define route params schema +const paramsSchema = z.object({ + pathId: z.string(), +}) + +export const Route = createFileRoute("/paths/$pathId")({ + component: ViewPath, + validateParams: (params) => paramsSchema.parse(params), +}) + +function ViewPath() { + const { pathId } = Route.useParams() + + return ( + + + View Path {pathId} + + + ) +} diff --git a/frontend/src/routes/paths/create/index.tsx b/frontend/src/routes/paths/create/index.tsx new file mode 100644 index 0000000000..8f7ded872f --- /dev/null +++ b/frontend/src/routes/paths/create/index.tsx @@ -0,0 +1,19 @@ +import { Container, Heading } from "@chakra-ui/react" +import { createFileRoute } from "@tanstack/react-router" +import React from "react" +import { z } from "zod" + +export const Route = createFileRoute("/paths/create")({ + component: CreatePath, + validateSearch: () => ({}), +}) + +function CreatePath() { + return ( + + + Create New Path + + + ) +} From e8472aa1cdf60aedcf67359e27fe8eccecd0d178 Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Sun, 2 Feb 2025 14:07:36 +0100 Subject: [PATCH 752/771] routing works from paths-menu. --- .../src/components/Common/ActionsMenu.tsx | 30 ++++++++-- .../src/components/Common/DeleteAlert.tsx | 29 +++++---- frontend/src/components/Common/Navbar.tsx | 38 ++++-------- .../src/components/Paths/AddPathButton.tsx | 26 ++++---- .../components/Paths/PathActionButtons.tsx | 40 +++++++++++++ .../src/components/Paths/PathActionsMenu.tsx | 60 +++++++++++++++++++ frontend/src/routeTree.gen.ts | 42 +++++++++---- frontend/src/routes/_layout/paths.tsx | 11 ++-- frontend/src/routes/paths/$pathId/index.tsx | 2 +- frontend/src/routes/paths/create/index.tsx | 2 +- 10 files changed, 206 insertions(+), 74 deletions(-) create mode 100644 frontend/src/components/Paths/PathActionButtons.tsx create mode 100644 frontend/src/components/Paths/PathActionsMenu.tsx diff --git a/frontend/src/components/Common/ActionsMenu.tsx b/frontend/src/components/Common/ActionsMenu.tsx index de06125482..800664642a 100644 --- a/frontend/src/components/Common/ActionsMenu.tsx +++ b/frontend/src/components/Common/ActionsMenu.tsx @@ -14,6 +14,8 @@ import EditUser from "../Admin/EditUser" import EditItem from "../Items/EditItem" import Delete from "./DeleteAlert" +import { useNavigate } from "@tanstack/react-router" + interface ActionsMenuProps { type: string value: ItemPublic | UserPublic | PathInList @@ -22,7 +24,20 @@ interface ActionsMenuProps { const ActionsMenu = ({ type, value, disabled }: ActionsMenuProps) => { const editUserModal = useDisclosure() + const editItemModal = useDisclosure() const deleteModal = useDisclosure() + const navigate = useNavigate() + + const handleEdit = () => { + if (type === "Path") { + const path = value as PathInList + navigate({ to: `/paths/${path.id}` }) // Match the route in /routes/paths/$pathId/index.tsx + } else if (type === "Item") { + editItemModal.onOpen() + } else if (type === "User") { + editUserModal.onOpen() + } + } return ( <> @@ -35,7 +50,11 @@ const ActionsMenu = ({ type, value, disabled }: ActionsMenuProps) => { /> { + console.log('Edit clicked, type:', type) + console.log('Edit clicked, value:', value) + handleEdit() + }} icon={} > Edit {type} @@ -48,17 +67,18 @@ const ActionsMenu = ({ type, value, disabled }: ActionsMenuProps) => { Delete {type} - {type === "User" ? ( + {type === "User" && ( - ) : ( + )} + {type === "Item" && ( )} { await ItemsService.deleteItem({ id: id }) } else if (type === "User") { await UsersService.deleteUser({ userId: id }) + } else if (type === "Path") { + await PathsService.deletePath({ pathId: id }) } else { throw new Error(`Unexpected type: ${type}`) } } const mutation = useMutation({ - mutationFn: deleteEntity, + mutationFn: () => deleteEntity(id), onSuccess: () => { showToast( "Success", `The ${type.toLowerCase()} was deleted successfully.`, "success", ) + // Invalidate the correct query based on type + if (type === "Path") { + queryClient.invalidateQueries({ queryKey: ["paths"] }) + } else if (type === "Item") { + queryClient.invalidateQueries({ queryKey: ["items"] }) + } else if (type === "User") { + queryClient.invalidateQueries({ queryKey: ["users"] }) + } onClose() }, onError: () => { showToast( - "An error occurred.", + "An error occurred", `An error occurred while deleting the ${type.toLowerCase()}.`, "error", ) }, - onSettled: () => { - queryClient.invalidateQueries({ - queryKey: [type === "Item" ? "items" : "users"], - }) - }, }) - const onSubmit = async () => { - mutation.mutate(id) - } + const onSubmit = handleSubmit(() => { + mutation.mutate() + }) return ( <> @@ -78,7 +83,7 @@ const Delete = ({ type, id, isOpen, onClose }: DeleteProps) => { isCentered > - + Delete {type} diff --git a/frontend/src/components/Common/Navbar.tsx b/frontend/src/components/Common/Navbar.tsx index 2aba31c362..a5d803901f 100644 --- a/frontend/src/components/Common/Navbar.tsx +++ b/frontend/src/components/Common/Navbar.tsx @@ -1,38 +1,26 @@ import type { ComponentType, ElementType } from "react" -import { Button, Flex, Icon, useDisclosure } from "@chakra-ui/react" +import { Button, Flex, Icon } from "@chakra-ui/react" import { FaPlus } from "react-icons/fa" +import { useNavigate } from "@tanstack/react-router" interface NavbarProps { type: string addModalAs: ComponentType | ElementType } -const Navbar = ({ type, addModalAs }: NavbarProps) => { - const addModal = useDisclosure() - - const AddModal = addModalAs +const Navbar = ({ type, addModalAs: AddButton }: NavbarProps) => { return ( - <> - - {/* TODO: Complete search functionality */} - {/* - - - - - */} - - - - + + {/* TODO: Complete search functionality */} + {/* + + + + + */} + + ) } diff --git a/frontend/src/components/Paths/AddPathButton.tsx b/frontend/src/components/Paths/AddPathButton.tsx index 16aecfe5a6..197ed6291f 100644 --- a/frontend/src/components/Paths/AddPathButton.tsx +++ b/frontend/src/components/Paths/AddPathButton.tsx @@ -1,18 +1,18 @@ +import { Button, Icon } from "@chakra-ui/react" import { useNavigate } from "@tanstack/react-router" +import { FaPlus } from "react-icons/fa" -interface AddPathButtonProps { - isOpen: boolean - onClose: () => void -} - -export default function AddPathButton({ isOpen, onClose }: AddPathButtonProps) { +export default function AddPathButton() { const navigate = useNavigate() - // Redirect to create page when "opened" - if (isOpen) { - navigate({ to: "/paths/create" }) - onClose() - } - - return null + return ( + + ) } diff --git a/frontend/src/components/Paths/PathActionButtons.tsx b/frontend/src/components/Paths/PathActionButtons.tsx new file mode 100644 index 0000000000..4053673b0d --- /dev/null +++ b/frontend/src/components/Paths/PathActionButtons.tsx @@ -0,0 +1,40 @@ +import { Button, ButtonGroup, Icon } from "@chakra-ui/react" +import { useNavigate } from "@tanstack/react-router" +import { FaEdit, FaTrash } from "react-icons/fa" + +interface PathActionButtonsProps { + pathId: string +} + +export default function PathActionButtons({ pathId }: PathActionButtonsProps) { + const navigate = useNavigate() + + const handleEdit = () => { + navigate({ to: `/paths/${pathId}` }) + } + + const handleDelete = () => { + // TODO: Implement delete functionality + console.log('Delete path:', pathId) + } + + return ( + + + + + ) +} \ No newline at end of file diff --git a/frontend/src/components/Paths/PathActionsMenu.tsx b/frontend/src/components/Paths/PathActionsMenu.tsx new file mode 100644 index 0000000000..830b141639 --- /dev/null +++ b/frontend/src/components/Paths/PathActionsMenu.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import { useNavigate } from '@tanstack/react-router'; + +interface PathActionsMenuProps { + pathId: string; +} + +const PathActionsMenu: React.FC = ({ pathId }) => { + const navigate = useNavigate(); + const [menuOpen, setMenuOpen] = useState(false); + + const handleEdit = () => { + navigate({ to: `/paths/${pathId}` }); + }; + + const handleDelete = () => { + // Placeholder for delete functionality + console.log('Delete action for path:', pathId); + }; + + return ( +
+ + {menuOpen && ( +
+
{ + handleEdit(); + setMenuOpen(false); + }} + className="cursor-pointer px-4 py-2 hover:bg-gray-100" + > + Edit Path +
+
{ + handleDelete(); + setMenuOpen(false); + }} + className="cursor-pointer px-4 py-2 hover:bg-gray-100" + > + Delete Path +
+
+ )} +
+ ); +}; + +export default PathActionsMenu; diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 7fa9279105..51c251531a 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -16,11 +16,13 @@ import { Route as ResetPasswordImport } from './routes/reset-password' import { Route as RecoverPasswordImport } from './routes/recover-password' import { Route as LoginImport } from './routes/login' import { Route as LayoutImport } from './routes/_layout' -import { Route as LayoutPathsImport } from './routes/_layout/paths' import { Route as LayoutIndexImport } from './routes/_layout/index' import { Route as LayoutSettingsImport } from './routes/_layout/settings' +import { Route as LayoutPathsImport } from './routes/_layout/paths' import { Route as LayoutItemsImport } from './routes/_layout/items' import { Route as LayoutAdminImport } from './routes/_layout/admin' +import { Route as PathsCreateIndexImport } from './routes/paths/create/index' +import { Route as PathsPathIdIndexImport } from './routes/paths/$pathId/index' // Create/Update Routes @@ -49,11 +51,6 @@ const LayoutRoute = LayoutImport.update({ getParentRoute: () => rootRoute, } as any) -const LayoutPathsRoute = LayoutPathsImport.update({ - path: '/paths', - getParentRoute: () => LayoutRoute, -} as any) - const LayoutIndexRoute = LayoutIndexImport.update({ path: '/', getParentRoute: () => LayoutRoute, @@ -64,6 +61,11 @@ const LayoutSettingsRoute = LayoutSettingsImport.update({ getParentRoute: () => LayoutRoute, } as any) +const LayoutPathsRoute = LayoutPathsImport.update({ + path: '/paths', + getParentRoute: () => LayoutRoute, +} as any) + const LayoutItemsRoute = LayoutItemsImport.update({ path: '/items', getParentRoute: () => LayoutRoute, @@ -74,6 +76,16 @@ const LayoutAdminRoute = LayoutAdminImport.update({ getParentRoute: () => LayoutRoute, } as any) +const PathsCreateIndexRoute = PathsCreateIndexImport.update({ + path: '/paths/create/', + getParentRoute: () => rootRoute, +} as any) + +const PathsPathIdIndexRoute = PathsPathIdIndexImport.update({ + path: '/paths/$pathId/', + getParentRoute: () => rootRoute, +} as any) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -106,6 +118,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutItemsImport parentRoute: typeof LayoutImport } + '/_layout/paths': { + preLoaderRoute: typeof LayoutPathsImport + parentRoute: typeof LayoutImport + } '/_layout/settings': { preLoaderRoute: typeof LayoutSettingsImport parentRoute: typeof LayoutImport @@ -114,9 +130,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutIndexImport parentRoute: typeof LayoutImport } - '/_layout/paths': { - preLoaderRoute: typeof LayoutPathsImport - parentRoute: typeof LayoutImport + '/paths/$pathId/': { + preLoaderRoute: typeof PathsPathIdIndexImport + parentRoute: typeof rootRoute + } + '/paths/create/': { + preLoaderRoute: typeof PathsCreateIndexImport + parentRoute: typeof rootRoute } } } @@ -127,14 +147,16 @@ export const routeTree = rootRoute.addChildren([ LayoutRoute.addChildren([ LayoutAdminRoute, LayoutItemsRoute, + LayoutPathsRoute, LayoutSettingsRoute, LayoutIndexRoute, - LayoutPathsRoute, ]), LoginRoute, RecoverPasswordRoute, ResetPasswordRoute, SignupRoute, + PathsPathIdIndexRoute, + PathsCreateIndexRoute, ]) /* prettier-ignore-end */ diff --git a/frontend/src/routes/_layout/paths.tsx b/frontend/src/routes/_layout/paths.tsx index dd44a87b7b..220d18a6a2 100644 --- a/frontend/src/routes/_layout/paths.tsx +++ b/frontend/src/routes/_layout/paths.tsx @@ -16,10 +16,10 @@ import { useEffect } from "react" import { z } from "zod" import { PathsService } from "../../client" -import ActionsMenu from "../../components/Common/ActionsMenu" import AddPathButton from "../../components/Paths/AddPathButton" import Navbar from "../../components/Common/Navbar" import { PaginationFooter } from "../../components/Common/PaginationFooter" +import PathActionButtons from '../../components/Paths/PathActionButtons'; const pathsSearchSchema = z.object({ page: z.number().catch(1), @@ -76,7 +76,7 @@ function PathsTable() { Title Summary - Actions + {isPending ? ( @@ -103,11 +103,8 @@ function PathsTable() { > {path.path_summary || "N/A"} - - + + ))} diff --git a/frontend/src/routes/paths/$pathId/index.tsx b/frontend/src/routes/paths/$pathId/index.tsx index 1a73d4a201..872b54a1d6 100644 --- a/frontend/src/routes/paths/$pathId/index.tsx +++ b/frontend/src/routes/paths/$pathId/index.tsx @@ -8,7 +8,7 @@ const paramsSchema = z.object({ pathId: z.string(), }) -export const Route = createFileRoute("/paths/$pathId")({ +export const Route = createFileRoute("/paths/$pathId/")({ component: ViewPath, validateParams: (params) => paramsSchema.parse(params), }) diff --git a/frontend/src/routes/paths/create/index.tsx b/frontend/src/routes/paths/create/index.tsx index 8f7ded872f..9f09b43561 100644 --- a/frontend/src/routes/paths/create/index.tsx +++ b/frontend/src/routes/paths/create/index.tsx @@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import React from "react" import { z } from "zod" -export const Route = createFileRoute("/paths/create")({ +export const Route = createFileRoute("/paths/create/")({ component: CreatePath, validateSearch: () => ({}), }) From 69c19c01e3dd175ce280dadce93619d9285812f8 Mon Sep 17 00:00:00 2001 From: Christoph Kenntemich Date: Sun, 2 Feb 2025 15:29:15 +0100 Subject: [PATCH 753/771] Prototype Create Interface - not yet linked to API --- frontend/package-lock.json | 133 ++++- frontend/package.json | 7 +- frontend/src/components/Common/Navbar.tsx | 38 +- .../components/Common/VideoRangeSlider.tsx | 68 +++ .../src/components/Common/YouTubePlayer.tsx | 88 +++ .../components/Paths/EXAMPLE_create-path.md | 560 ++++++++++++++++++ .../src/components/Paths/PathActionsMenu.tsx | 60 -- frontend/src/routes/_layout/paths.tsx | 6 +- frontend/src/routes/paths/create/index.tsx | 344 ++++++++++- frontend/src/utils/youtube.ts | 15 + 10 files changed, 1235 insertions(+), 84 deletions(-) create mode 100644 frontend/src/components/Common/VideoRangeSlider.tsx create mode 100644 frontend/src/components/Common/YouTubePlayer.tsx create mode 100644 frontend/src/components/Paths/EXAMPLE_create-path.md delete mode 100644 frontend/src/components/Paths/PathActionsMenu.tsx create mode 100644 frontend/src/utils/youtube.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 661ce88605..faf25df0a8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,17 +12,20 @@ "@chakra-ui/react": "2.8.2", "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", + "@hookform/resolvers": "^3.10.0", "@tanstack/react-query": "^5.28.14", "@tanstack/react-query-devtools": "^5.28.14", "@tanstack/react-router": "1.19.1", + "@types/youtube": "^0.1.0", "axios": "1.7.4", "form-data": "4.0.0", "framer-motion": "10.16.16", "react": "^18.2.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.13", - "react-hook-form": "7.49.3", - "react-icons": "5.0.1" + "react-hook-form": "^7.49.3", + "react-icons": "5.0.1", + "react-youtube": "^10.1.0" }, "devDependencies": { "@biomejs/biome": "1.6.1", @@ -2106,6 +2109,14 @@ "typescript": "^5.x" } }, + "node_modules/@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -2775,6 +2786,11 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", "devOptional": true }, + "node_modules/@types/youtube": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@types/youtube/-/youtube-0.1.0.tgz", + "integrity": "sha512-Pg33m3X2mFgdmhtvzOlAfUfgOa3341N3/2JCrVY/mXVxb4hagcqqEG6w4vGCfB64StQNWHSj/T8Eotb1Rko/FQ==" + }, "node_modules/@vitejs/plugin-react-swc": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", @@ -3067,6 +3083,14 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", @@ -3186,6 +3210,11 @@ "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==" + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -3530,6 +3559,11 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==" + }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", @@ -3657,6 +3691,11 @@ "ufo": "^1.5.4" } }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -4133,6 +4172,22 @@ } } }, + "node_modules/react-youtube": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-10.1.0.tgz", + "integrity": "sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==", + "dependencies": { + "fast-deep-equal": "3.1.3", + "prop-types": "15.8.1", + "youtube-player": "5.5.2" + }, + "engines": { + "node": ">= 14.x" + }, + "peerDependencies": { + "react": ">=0.14.1" + } + }, "node_modules/readdirp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", @@ -4255,6 +4310,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sister": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz", + "integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA==" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4528,6 +4588,16 @@ "node": ">= 6" } }, + "node_modules/youtube-player": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz", + "integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==", + "dependencies": { + "debug": "^2.6.6", + "load-script": "^1.0.0", + "sister": "^3.0.0" + } + }, "node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", @@ -5960,6 +6030,12 @@ "handlebars": "4.7.8" } }, + "@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "requires": {} + }, "@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -6344,6 +6420,11 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", "devOptional": true }, + "@types/youtube": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@types/youtube/-/youtube-0.1.0.tgz", + "integrity": "sha512-Pg33m3X2mFgdmhtvzOlAfUfgOa3341N3/2JCrVY/mXVxb4hagcqqEG6w4vGCfB64StQNWHSj/T8Eotb1Rko/FQ==" + }, "@vitejs/plugin-react-swc": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", @@ -6565,6 +6646,14 @@ "@babel/runtime": "^7.21.0" } }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, "defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", @@ -6653,6 +6742,11 @@ "strip-final-newline": "^3.0.0" } }, + "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==" + }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -6895,6 +6989,11 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==" + }, "lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", @@ -6984,6 +7083,11 @@ "ufo": "^1.5.4" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -7283,6 +7387,16 @@ "tslib": "^2.0.0" } }, + "react-youtube": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-10.1.0.tgz", + "integrity": "sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==", + "requires": { + "fast-deep-equal": "3.1.3", + "prop-types": "15.8.1", + "youtube-player": "5.5.2" + } + }, "readdirp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", @@ -7364,6 +7478,11 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true }, + "sister": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz", + "integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7517,6 +7636,16 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, + "youtube-player": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz", + "integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==", + "requires": { + "debug": "^2.6.6", + "load-script": "^1.0.0", + "sister": "^3.0.0" + } + }, "zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 546028c9a9..fd578faeaf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,17 +15,20 @@ "@chakra-ui/react": "2.8.2", "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", + "@hookform/resolvers": "^3.10.0", "@tanstack/react-query": "^5.28.14", "@tanstack/react-query-devtools": "^5.28.14", "@tanstack/react-router": "1.19.1", + "@types/youtube": "^0.1.0", "axios": "1.7.4", "form-data": "4.0.0", "framer-motion": "10.16.16", "react": "^18.2.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.13", - "react-hook-form": "7.49.3", - "react-icons": "5.0.1" + "react-hook-form": "^7.49.3", + "react-icons": "5.0.1", + "react-youtube": "^10.1.0" }, "devDependencies": { "@biomejs/biome": "1.6.1", diff --git a/frontend/src/components/Common/Navbar.tsx b/frontend/src/components/Common/Navbar.tsx index a5d803901f..2aba31c362 100644 --- a/frontend/src/components/Common/Navbar.tsx +++ b/frontend/src/components/Common/Navbar.tsx @@ -1,26 +1,38 @@ import type { ComponentType, ElementType } from "react" -import { Button, Flex, Icon } from "@chakra-ui/react" +import { Button, Flex, Icon, useDisclosure } from "@chakra-ui/react" import { FaPlus } from "react-icons/fa" -import { useNavigate } from "@tanstack/react-router" interface NavbarProps { type: string addModalAs: ComponentType | ElementType } -const Navbar = ({ type, addModalAs: AddButton }: NavbarProps) => { +const Navbar = ({ type, addModalAs }: NavbarProps) => { + const addModal = useDisclosure() + + const AddModal = addModalAs return ( - - {/* TODO: Complete search functionality */} - {/* - - - - - */} - - + <> + + {/* TODO: Complete search functionality */} + {/* + + + + + */} + + + + ) } diff --git a/frontend/src/components/Common/VideoRangeSlider.tsx b/frontend/src/components/Common/VideoRangeSlider.tsx new file mode 100644 index 0000000000..7139557fb9 --- /dev/null +++ b/frontend/src/components/Common/VideoRangeSlider.tsx @@ -0,0 +1,68 @@ +import { + Box, + RangeSlider, + RangeSliderTrack, + RangeSliderFilledTrack, + RangeSliderThumb, + Text, +} from "@chakra-ui/react" + +interface VideoRangeSliderProps { + duration: number + start: number + end: number + onChange: (start: number, end: number) => void +} + +function formatTime(seconds: number): string { + const minutes = Math.floor(seconds / 60) + const remainingSeconds = Math.floor(seconds % 60) + return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}` +} + +export function VideoRangeSlider({ + duration, + start, + end, + onChange, +}: VideoRangeSliderProps) { + return ( + + onChange(newStart, newEnd)} + > + + + + + + {formatTime(start)} + + + + + {formatTime(end)} + + + + + Total Duration: {formatTime(duration)} + + + ) +} diff --git a/frontend/src/components/Common/YouTubePlayer.tsx b/frontend/src/components/Common/YouTubePlayer.tsx new file mode 100644 index 0000000000..d1f87ac1fd --- /dev/null +++ b/frontend/src/components/Common/YouTubePlayer.tsx @@ -0,0 +1,88 @@ +import { useEffect, useRef } from "react" +import YouTube, { YouTubeEvent, YouTubePlayer as YTPlayer } from "react-youtube" +import { Box } from "@chakra-ui/react" + +interface YouTubePlayerProps { + videoId: string + start?: number // Start time in seconds + end?: number // End time in seconds + onReady?: (duration: number) => void + className?: string +} + +export function YouTubePlayer({ + videoId, + start, + end, + onReady, + className, +}: YouTubePlayerProps) { + const playerRef = useRef() + const checkIntervalRef = useRef>() + + useEffect(() => { + return () => { + if (checkIntervalRef.current) { + clearInterval(checkIntervalRef.current) + } + } + }, []) + + const handleReady = (event: YouTubeEvent) => { + const player = event.target + playerRef.current = player + + // Get video duration and notify parent + const duration = player.getDuration() + onReady?.(duration) + + if (start) { + player.seekTo(start, true) + } + + // Set up interval to check time and reset if needed + if (end) { + checkIntervalRef.current = setInterval(() => { + const currentTime = player.getCurrentTime() + if (currentTime >= end) { + player.seekTo(start || 0, true) + player.pauseVideo() + } + }, 1000) + } + } + + const handleStateChange = (event: YouTubeEvent) => { + const player = event.target + // If video starts playing and we have a start time, ensure we're at the right point + if (event.data === YouTube.PlayerState.PLAYING && start) { + const currentTime = player.getCurrentTime() + if (currentTime < start) { + player.seekTo(start, true) + } + } + } + + return ( + + + + + + ) +} diff --git a/frontend/src/components/Paths/EXAMPLE_create-path.md b/frontend/src/components/Paths/EXAMPLE_create-path.md new file mode 100644 index 0000000000..15e53901d1 --- /dev/null +++ b/frontend/src/components/Paths/EXAMPLE_create-path.md @@ -0,0 +1,560 @@ +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import * as z from "zod" +import { Plus, Video, FileText, X, Eye } from "lucide-react" +import { useState } from "react" +import { ToastProvider, Toast, ToastTitle, ToastDescription, ToastViewport } from "@/components/ui/toast" + +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { Checkbox } from "@/components/ui/checkbox" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { StepPreview } from "./step-preview" +import { PathPreview } from "./path-preview" +import { YouTubePlayer } from "@/components/ui/youtube-player" +import { VideoRangeSlider } from "@/components/ui/video-range-slider" +import { extractVideoId } from "@/lib/youtube" + +// Validation schema for a single step +const stepSchema = z.object({ + id: z.number(), + title: z.string().min(1, "Titel ist erforderlich"), + content: z.object({ + type: z.enum(["video", "text"]), + source: z.string(), + segment: z.object({ + start: z.number(), + end: z.number(), + }), + continueFromPrevious: z.boolean(), + }), + rolePrompt: z.string().min(1, "Rolle ist erforderlich"), + validationPrompt: z.string().min(1, "Validierung ist erforderlich") +}) + +// Validation schema for the entire path +const pathSchema = z.object({ + title: z.string().min(1, "Titel ist erforderlich"), + pathSummary: z.string().min(1, "Lernziel ist erforderlich"), + overallObjective: z.string().min(1, "Gesamtziel ist erforderlich"), + interactionStyle: z.literal("chat"), + language: z.literal("de"), + moduleId: z.number().optional(), + steps: z.array(stepSchema), +}) + +// Type for the form state +type FormStep = { + id: number + title: string + content: { + type: "video" | "text" + source: string + segment: { + start: number + end: number + } + continueFromPrevious: boolean + } + rolePrompt: string + validationPrompt: string +} + +type FormData = { + title: string + pathSummary: string + overallObjective: string + interactionStyle: "chat" + language: "de" + moduleId?: number + steps: FormStep[] +} + +const defaultValues: Partial = { + title: "", + pathSummary: "", + overallObjective: "", + interactionStyle: "chat", + language: "de", + steps: [], +} + +export function PathEditor() { + const [previewStep, setPreviewStep] = useState(null) + const [showFullPreview, setShowFullPreview] = useState(false) + const [videoDurations, setVideoDurations] = useState>({}) + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState(null) + const [previewMode, setPreviewMode] = useState(false) + const [showToast, setShowToast] = useState(false) + + const form = useForm({ + resolver: zodResolver(pathSchema), + defaultValues, + }) + + async function onSubmit(data: FormData) { + try { + setIsSubmitting(true) + setError(null) + + const response = await fetch('/api/create/paths', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...data, + creatorId: 1, // TODO: Get from auth context + }), + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to create path') + } + + const path = await response.json() + + // After creating the path, create each step + for (const step of data.steps) { + const stepResponse = await fetch(`/api/create/paths/${path.id}/steps`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + title: step.title, + role_prompt: { + content: step.rolePrompt + }, + validation_prompt: { + content: step.validationPrompt + }, + exposition: step.content.type ? { + type: step.content.type, + source: step.content.source, + segment: step.content.segment + } : undefined + }), + }) + + if (!stepResponse.ok) { + throw new Error('Failed to create step') + } + } + + setShowToast(true) + + // Redirect after a short delay to show the success message + setTimeout(() => { + window.location.href = `/paths/${path.id}` + }, 2000) + } catch (error) { + console.error('Error creating path:', error) + setError('Fehler beim Speichern des Lernpfads. Bitte versuchen Sie es erneut.') + } finally { + setIsSubmitting(false) + } + } + + if (previewMode) { + return ( + setPreviewMode(false)} + /> + ) + } + + if (showFullPreview) { + return ( + setShowFullPreview(false)} + /> + ) + } + + return ( +
+
+ + {error && ( +
+ {error} +
+ )} + {/* Path Information */} +
+

Pfad-Informationen

+ ( + + Titel + + + + + + )} + /> + + ( + + Lernziel + +