From 4941b5a154de59aa2db56a11a921f85d17a35d78 Mon Sep 17 00:00:00 2001 From: Kilton937342 Date: Wed, 22 Feb 2023 12:43:39 +0100 Subject: [PATCH] tests --- .idea/fastapi_gen.iml | 9 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 84 + backend/api/.idea/.gitignore | 8 + backend/api/.idea/api.iml | 11 + .../api/.idea/codeStyles/codeStyleConfig.xml | 5 + backend/api/.idea/dataSources.xml | 24 + backend/api/.idea/dbnavigator.xml | 423 +++ backend/api/.idea/misc.xml | 6 + backend/api/.idea/modules.xml | 8 + backend/api/.idea/vcs.xml | 6 + backend/api/database/auth/crud.py | 15 +- backend/api/database/auth/models.py | 17 +- backend/api/database/db.py | 4 +- backend/api/database/exercices/models.py | 2 +- backend/api/database/room/crud.py | 553 +++- backend/api/database/room/models.py | 419 ++- backend/api/database2.db | Bin 0 -> 724992 bytes backend/api/database3.db | Bin 0 -> 143360 bytes backend/api/database4.db | Bin 0 -> 118784 bytes backend/api/database5.db | Bin 0 -> 118784 bytes backend/api/database6.db | Bin 0 -> 159744 bytes backend/api/database7.db | Bin 0 -> 135168 bytes backend/api/faking/room.py | 0 backend/api/generateur/generateur_csv.py | 1 + backend/api/generateur/generateur_main.py | 74 +- backend/api/main.py | 49 +- backend/api/routes/auth/routes.py | 43 +- backend/api/routes/exercices/routes.py | 125 +- backend/api/routes/room/consumer.py | 145 +- backend/api/routes/room/manager.py | 18 +- backend/api/routes/room/routes.py | 219 +- backend/api/services/websocket.py | 8 +- backend/api/tests/test_exos.py | 79 +- backend/api/tests/test_room.py | 736 +++-- .../testing_exo_source/exo_source_web_only.py | 2 +- frontend/.idea/.gitignore | 8 + frontend/.idea/dbnavigator.xml | 420 +++ frontend/.idea/frontend.iml | 9 + .../inspectionProfiles/Project_Default.xml | 6 + frontend/.idea/misc.xml | 6 + frontend/.idea/modules.xml | 8 + frontend/.idea/vcs.xml | 6 + frontend/cypress.config.ts | 9 + frontend/cypress/e2e/exos.cy.ts | 7 + frontend/cypress/fixtures/example.json | 5 + frontend/cypress/support/commands.ts | 37 + frontend/cypress/support/e2e.ts | 20 + frontend/package.json | 16 +- frontend/pnpm-lock.yaml | 2534 ++++++++++++++++- frontend/src/apis/auth.api.ts | 15 + frontend/src/apis/exo.api.ts | 9 +- frontend/src/apis/room.api.ts | 19 + frontend/src/app.scss | 52 +- frontend/src/components/NavBar.svelte | 207 ++ frontend/src/components/NavLink.svelte | 13 +- frontend/src/components/auth/InfoForm.svelte | 46 + .../src/components/auth/PasswordForm.svelte | 31 + frontend/src/components/auth/RoomList.svelte | 40 + frontend/src/components/auth/Section.svelte | 53 + .../src/components/auth/UserConfirm.svelte | 35 + frontend/src/components/exos/Card.svelte | 74 +- .../src/components/exos/CreateCard.svelte | 5 +- .../src/components/exos/DownloadForm.svelte | 36 +- frontend/src/components/exos/EditForm.svelte | 18 +- .../components/exos/ExerciceSelector.svelte | 132 + frontend/src/components/exos/ExoList.svelte | 165 ++ frontend/src/components/exos/Feed.svelte | 170 +- frontend/src/components/exos/Head.svelte | 38 +- .../src/components/exos/Pagination.svelte | 140 +- frontend/src/components/exos/Tag.svelte | 57 +- .../src/components/exos/TagContainer.svelte | 13 +- frontend/src/components/exos/TagViewer.svelte | 53 + .../src/components/forms/FileInput.svelte | 5 +- .../components/forms/InputWithLabel.svelte | 22 +- frontend/src/components/forms/Item.svelte | 10 +- .../src/components/forms/LabeledInput.svelte | 96 + .../src/components/forms/TagSelector.svelte | 7 + .../src/components/rooms/AskPseudo.svelte | 37 + .../src/components/rooms/Challenge.svelte | 290 ++ .../components/rooms/ChallengesList.svelte | 113 + .../src/components/rooms/Classement.svelte | 41 + .../components/rooms/InputChallenge.svelte | 184 ++ frontend/src/components/rooms/Members.svelte | 206 ++ frontend/src/components/rooms/Note.svelte | 41 + .../components/rooms/ParcoursCreate.svelte | 143 + .../components/rooms/ParcoursDetails.svelte | 261 ++ .../src/components/rooms/ParcoursList.svelte | 128 + frontend/src/components/rooms/RoomHead.svelte | 149 + frontend/src/components/rooms/Stats.svelte | 118 + frontend/src/context/Auth.svelte | 146 +- frontend/src/context/Modal.svelte | 6 +- frontend/src/context/Navigation.svelte | 24 +- frontend/src/context/Notification.svelte | 160 ++ frontend/src/mixins.scss | 32 + frontend/src/requests/auth.request.ts | 132 +- frontend/src/requests/exo.request.ts | 25 + frontend/src/requests/room.request.ts | 158 + frontend/src/routes/+layout.svelte | 173 +- frontend/src/routes/+page.svelte | 11 +- frontend/src/routes/dashboard/+page.svelte | 79 + .../routes/exercices/[[slug]]/+page.svelte | 1 - frontend/src/routes/room/+page.svelte | 16 + .../src/routes/room/[...slug]/+page.svelte | 458 +++ frontend/src/routes/room/create/+page.svelte | 53 + frontend/src/routes/room/join/+page.svelte | 38 + frontend/src/routes/signin/+page.svelte | 68 + frontend/src/routes/signup/+page.svelte | 107 +- frontend/src/store/ws.ts | 33 + frontend/src/test/ContextTest.svelte | 18 + frontend/src/test/exo.test.ts | 922 ++++++ frontend/src/test/room.test.ts | 1137 ++++++++ frontend/src/types/auth.type.ts | 14 + frontend/src/types/exo.type.ts | 4 +- frontend/src/types/room.type.ts | 103 + frontend/src/utils/forms.ts | 4 +- frontend/src/utils/utils.ts | 63 +- frontend/src/variables.scss | 4 +- frontend/vite.config.js | 5 + 120 files changed, 12235 insertions(+), 1229 deletions(-) create mode 100644 .idea/fastapi_gen.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 backend/api/.idea/.gitignore create mode 100644 backend/api/.idea/api.iml create mode 100644 backend/api/.idea/codeStyles/codeStyleConfig.xml create mode 100644 backend/api/.idea/dataSources.xml create mode 100644 backend/api/.idea/dbnavigator.xml create mode 100644 backend/api/.idea/misc.xml create mode 100644 backend/api/.idea/modules.xml create mode 100644 backend/api/.idea/vcs.xml create mode 100644 backend/api/database2.db create mode 100644 backend/api/database3.db create mode 100644 backend/api/database4.db create mode 100644 backend/api/database5.db create mode 100644 backend/api/database6.db create mode 100644 backend/api/database7.db create mode 100644 backend/api/faking/room.py create mode 100644 frontend/.idea/.gitignore create mode 100644 frontend/.idea/dbnavigator.xml create mode 100644 frontend/.idea/frontend.iml create mode 100644 frontend/.idea/inspectionProfiles/Project_Default.xml create mode 100644 frontend/.idea/misc.xml create mode 100644 frontend/.idea/modules.xml create mode 100644 frontend/.idea/vcs.xml create mode 100644 frontend/cypress.config.ts create mode 100644 frontend/cypress/e2e/exos.cy.ts create mode 100644 frontend/cypress/fixtures/example.json create mode 100644 frontend/cypress/support/commands.ts create mode 100644 frontend/cypress/support/e2e.ts create mode 100644 frontend/src/apis/auth.api.ts create mode 100644 frontend/src/apis/room.api.ts create mode 100644 frontend/src/components/NavBar.svelte create mode 100644 frontend/src/components/auth/InfoForm.svelte create mode 100644 frontend/src/components/auth/PasswordForm.svelte create mode 100644 frontend/src/components/auth/RoomList.svelte create mode 100644 frontend/src/components/auth/Section.svelte create mode 100644 frontend/src/components/auth/UserConfirm.svelte create mode 100644 frontend/src/components/exos/ExerciceSelector.svelte create mode 100644 frontend/src/components/exos/ExoList.svelte create mode 100644 frontend/src/components/exos/TagViewer.svelte create mode 100644 frontend/src/components/forms/LabeledInput.svelte create mode 100644 frontend/src/components/rooms/AskPseudo.svelte create mode 100644 frontend/src/components/rooms/Challenge.svelte create mode 100644 frontend/src/components/rooms/ChallengesList.svelte create mode 100644 frontend/src/components/rooms/Classement.svelte create mode 100644 frontend/src/components/rooms/InputChallenge.svelte create mode 100644 frontend/src/components/rooms/Members.svelte create mode 100644 frontend/src/components/rooms/Note.svelte create mode 100644 frontend/src/components/rooms/ParcoursCreate.svelte create mode 100644 frontend/src/components/rooms/ParcoursDetails.svelte create mode 100644 frontend/src/components/rooms/ParcoursList.svelte create mode 100644 frontend/src/components/rooms/RoomHead.svelte create mode 100644 frontend/src/components/rooms/Stats.svelte create mode 100644 frontend/src/context/Notification.svelte create mode 100644 frontend/src/mixins.scss create mode 100644 frontend/src/requests/room.request.ts create mode 100644 frontend/src/routes/dashboard/+page.svelte create mode 100644 frontend/src/routes/room/+page.svelte create mode 100644 frontend/src/routes/room/[...slug]/+page.svelte create mode 100644 frontend/src/routes/room/create/+page.svelte create mode 100644 frontend/src/routes/room/join/+page.svelte create mode 100644 frontend/src/routes/signin/+page.svelte create mode 100644 frontend/src/store/ws.ts create mode 100644 frontend/src/test/ContextTest.svelte create mode 100644 frontend/src/test/exo.test.ts create mode 100644 frontend/src/test/room.test.ts create mode 100644 frontend/src/types/auth.type.ts create mode 100644 frontend/src/types/room.type.ts diff --git a/.idea/fastapi_gen.iml b/.idea/fastapi_gen.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/fastapi_gen.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d46312a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..f649b90 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1676499669960 + + + + \ No newline at end of file diff --git a/backend/api/.idea/.gitignore b/backend/api/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/backend/api/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/backend/api/.idea/api.iml b/backend/api/.idea/api.iml new file mode 100644 index 0000000..2cdb1e3 --- /dev/null +++ b/backend/api/.idea/api.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/backend/api/.idea/codeStyles/codeStyleConfig.xml b/backend/api/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/backend/api/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backend/api/.idea/dataSources.xml b/backend/api/.idea/dataSources.xml new file mode 100644 index 0000000..3ec678d --- /dev/null +++ b/backend/api/.idea/dataSources.xml @@ -0,0 +1,24 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/database.db + $ProjectFileDir$ + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/database7.db + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.39.2/sqlite-jdbc-3.39.2.jar + + + + + \ No newline at end of file diff --git a/backend/api/.idea/dbnavigator.xml b/backend/api/.idea/dbnavigator.xml new file mode 100644 index 0000000..50a6953 --- /dev/null +++ b/backend/api/.idea/dbnavigator.xml @@ -0,0 +1,423 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/api/.idea/misc.xml b/backend/api/.idea/misc.xml new file mode 100644 index 0000000..fdbdefa --- /dev/null +++ b/backend/api/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/api/.idea/modules.xml b/backend/api/.idea/modules.xml new file mode 100644 index 0000000..d50cf45 --- /dev/null +++ b/backend/api/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/api/.idea/vcs.xml b/backend/api/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/backend/api/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/api/database/auth/crud.py b/backend/api/database/auth/crud.py index 02d7f04..43e40db 100644 --- a/backend/api/database/auth/crud.py +++ b/backend/api/database/auth/crud.py @@ -1,9 +1,14 @@ import uuid -from services.password import get_password_hash -from database.auth.models import User, UserEdit -from sqlmodel import Session, select + from jose import jwt, exceptions +from sqlmodel import Session, select + from config import SECRET_KEY, ALGORITHM +from database.auth.models import User, UserEdit +from database.room.models import Member +from services.password import get_password_hash + + def create_user_db(username:str , password: str, db: Session): user = User(username=username, hashed_password=password, clientId=uuid.uuid4()) db.add(user) @@ -84,3 +89,7 @@ def change_user_uuid(id: int, db: Session): db.refresh(user) return user.clientId + +def parse_user_rooms(user: User, db: Session): + members = db.exec(select(Member).where(Member.user_id == user.id)).all() + return [{"name": m.room.name, "id_code": m.room.id_code, "admin": m.is_admin} for m in members] diff --git a/backend/api/database/auth/models.py b/backend/api/database/auth/models.py index 5a37d66..cf22f63 100644 --- a/backend/api/database/auth/models.py +++ b/backend/api/database/auth/models.py @@ -1,9 +1,11 @@ -from typing import List, Optional +import uuid +from typing import List from typing import TYPE_CHECKING, Optional from uuid import UUID -import uuid -from sqlmodel import Field, SQLModel, Relationship + from pydantic import validator, BaseModel +from sqlmodel import Field, SQLModel, Relationship + from services.password import validate_password from services.schema import as_form @@ -29,9 +31,16 @@ class User(UserBase, table=True): @as_form class UserEdit(UserBase): pass - +class UsersRoom(BaseModel): + name: str + id_code: str + admin: bool = False class UserRead(UserBase): id: int + rooms: List[UsersRoom] = [] +class UserEditRead(UserBase): + id: int + #rooms: List[UsersRoom] = [] @as_form diff --git a/backend/api/database/db.py b/backend/api/database/db.py index ef4ef1b..c3c2a0d 100644 --- a/backend/api/database/db.py +++ b/backend/api/database/db.py @@ -2,7 +2,7 @@ import pydantic.json import json from sqlmodel import SQLModel, create_engine, Session, select -sqlite_file_name = "database.db" +sqlite_file_name = "database7.db" sqlite_url = f"sqlite:///{sqlite_file_name}" @@ -19,6 +19,6 @@ def create_db_and_tables(): SQLModel.metadata.create_all(engine) def get_session(): - with Session(engine) as s: + with Session(engine, expire_on_commit=False) as s: yield s diff --git a/backend/api/database/exercices/models.py b/backend/api/database/exercices/models.py index 388c2ce..6defdd9 100644 --- a/backend/api/database/exercices/models.py +++ b/backend/api/database/exercices/models.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: from database.auth.models import User -class ExampleEnum(Enum): +class ExampleEnum(str, Enum): csv = 'csv' pdf = 'pdf' web = 'web' diff --git a/backend/api/database/room/crud.py b/backend/api/database/room/crud.py index e2594b6..933dcb4 100644 --- a/backend/api/database/room/crud.py +++ b/backend/api/database/room/crud.py @@ -1,17 +1,21 @@ -from services.auth import get_current_user_optional -from services.misc import noteOn20 -from copy import deepcopy -from typing import Dict, List import uuid -from fastapi import Body, Depends, HTTPException, status +from copy import deepcopy +from typing import List + +from fastapi import Depends, HTTPException, status, Query from pydantic import BaseModel -from sqlmodel import Session, delete, select, col, table -from database.db import get_session -from database.room.models import Anonymous, Challenge, Challenges, CorrigedGeneratorOut, Exercices, ExercicesCreate, Member, Note, Parcours, ParcoursCreate, ParcoursReadShort, ParsedGeneratorOut, Room, RoomCreate, RoomInfo, RoomRead, TmpCorrection, Waiter, MemberRead -from database.auth.models import User -from services.database import generate_unique_code +from sqlalchemy import func +from sqlmodel import Session, delete, select, col + from database.auth.crud import get_user_from_token +from database.auth.models import User +from database.db import get_session from database.exercices.models import Exercice +from database.room.models import Anonymous, Challenge, Challenges, CorrigedGeneratorOut, Exercices, ExercicesCreate, \ + Member, Note, Parcours, ParcoursCreate, ParcoursReadShort, ParsedGeneratorOut, Room, RoomCreate, RoomInfo, \ + TmpCorrection, Waiter, MemberRead, CorrigedData, CorrectionData, Challenger +from services.auth import get_current_user_optional +from services.database import generate_unique_code def create_room_db(*, room: RoomCreate, user: User | None = None, username: str | None = None, db: Session): @@ -38,12 +42,15 @@ def create_room_db(*, room: RoomCreate, user: User | None = None, username: str return {"room": room_obj, "member": member} + def change_room_name(room: Room, name: str, db: Session): room.name = name db.add(room) db.commit() db.refresh(room) return room + + def change_room_status(room: Room, public: bool, db: Session): room.public = public db.add(room) @@ -51,6 +58,7 @@ def change_room_status(room: Room, public: bool, db: Session): db.refresh(room) return room + def get_member_from_user(user_id: int, room_id: int, db: Session): member = db.exec(select(Member).where(Member.room_id == room_id, Member.user_id == user_id)).first() @@ -86,6 +94,7 @@ def get_anonymous_from_code(reconnect_code: str, db: Session): Anonymous.reconnect_code == reconnect_code)).first() return anonymous + def get_anonymous_from_clientId(clientId: str, db: Session): anonymous = db.exec(select(Anonymous).where( Anonymous.clientId == clientId)).first() @@ -102,20 +111,23 @@ def get_member_from_clientId(clientId: str, room_id: int, db: Session): def create_member(*, room: Room, user: User | None = None, anonymous: Anonymous | None = None, waiting: bool = False, db: Session): member_id = generate_unique_code(Member, s=db) - member = Member(room=room, user=user, anonymous=anonymous, waiting=waiting, + member = Member(room=room, user=user, anonymous=anonymous, waiting=waiting, id_code=member_id) + member.online = True db.add(member) db.commit() db.refresh(member) return member -def get_or_create_member(*, room: Room, user: User | None = None, anonymous: Anonymous | None = None, waiting: bool = False, db: Session): - member = user is not None and get_member_from_user(user.id, room.id, db) +def get_or_create_member(*, room: Room, user: User | None = None, anonymous: Anonymous | None = None, + waiting: bool = False, db: Session): + member = user is not None and get_member_from_user(user.id, room.id, db) if member is not None and member is not False: return member - member= create_member(room=room, user=user, anonymous=anonymous, waiting=waiting, db=db) - + member = create_member(room=room, user=user, + anonymous=anonymous, waiting=waiting, db=db) + def connect_member(member: Member, db: Session): member.online = True @@ -128,10 +140,10 @@ def connect_member(member: Member, db: Session): def disconnect_member(member: Member, db: Session): if member.waiting == False: member.online = False - + if member.anonymous is not None: - change_anonymous_clientId(member.anonymous,db) - + change_anonymous_clientId(member.anonymous, db) + db.add(member) db.commit() db.refresh(member) @@ -167,6 +179,7 @@ def create_anonymous_member(username: str, room: Room, db: Session): db.refresh(member) return member + def create_anonymous(username: str, room: Room, db: Session): username = validate_username(username, room, db) if username is None: @@ -179,10 +192,13 @@ def create_anonymous(username: str, room: Room, db: Session): db.refresh(anonymous) return anonymous + def check_user_in_room(user_id: int, room_id: int, db: Session): - user = db.exec(select(Member).where(Member.user_id==user_id, Member.room_id == room_id)).first() + user = db.exec(select(Member).where(Member.user_id == + user_id, Member.room_id == room_id)).first() return user + def create_user_member(user: User, room: Room, db: Session): member = get_member_from_user(user.id, room.id, db) if member is not None: @@ -194,6 +210,7 @@ def create_user_member(user: User, room: Room, db: Session): db.refresh(member) return member + def create_anonymous_waiter(username: str, room: Room, db: Session): username = validate_username(username, room, db) if username is None: @@ -210,6 +227,7 @@ def create_anonymous_waiter(username: str, room: Room, db: Session): db.refresh(member) return member + def create_user_waiter(user: User, room: Room, db: Session): member = get_member_from_user(user.id, room.id, db) if member is not None: @@ -219,6 +237,7 @@ def create_user_waiter(user: User, room: Room, db: Session): db=db) return member + def get_waiter(waiter_code: str, db: Session): return db.exec(select(Member).where(Member.id_code == waiter_code, Member.waiting == True)).first() @@ -236,6 +255,7 @@ def delete_member(member: Member, db: Session): def accept_waiter(member: Member, db: Session): member.waiting = False member.waiter_code = None + member.online = True db.add(member) db.commit() db.refresh(member) @@ -254,39 +274,173 @@ def leave_room(member: Member, db: Session): return None - -def serialize_member(member: Member) -> MemberRead | Waiter: +def serialize_member(member: Member, private: bool = False, admin: bool = False, + m2: Member | None = None) -> MemberRead | Waiter: member_obj = member.user or member.anonymous - if member.waiting == False: - return MemberRead(username=member_obj.username, reconnect_code=getattr(member_obj, "reconnect_code", ""), isUser=member.user_id != None, isAdmin=member.is_admin, id_code=member.id_code).dict() - if member.waiting == True: + print("OHLA", member_obj, private, member.user_id == None) + if not member.waiting: + return MemberRead(username=member_obj.username, online=member.online, + clientId=str(member_obj.clientId) if (private == True and member.user_id == None) else "", + reconnect_code=getattr(member_obj, "reconnect_code", "") if (admin or m2 == member) else "", + isUser=member.user_id != None, isAdmin=member.is_admin, id_code=member.id_code).dict() + if member.waiting: return Waiter(username=member_obj.username, waiter_id=member.id_code).dict() + def serialize_parcours_short(parcours: Parcours, member: Member, db: Session): - best_note = db.exec(select(Challenge.note, Challenge.time).where(Challenge.parcours_id == parcours.id, Challenge.challenger_id == member.id).order_by(col(Challenge.note).desc()).limit(1)).first() - note = None - if best_note is not None: - best_note=best_note[0] - note = Note(note=best_note[0], time=best_note[1]) - return ParcoursReadShort(**parcours.dict(exclude_unset=True), best_note=note) + challenger = getChallenger(parcours, member, db) + + return ParcoursReadShort(name=parcours.name, id_code=parcours.id_code, best_note=challenger.best, + validated=challenger.validated) + def serialize_challenge(challenge: Challenge): - return Challenges(name=challenge.challenger.user.username if challenge.challenger.user is not None else challenge.challenger.anonymous.username, value=Note(note=challenge.note, time=challenge.time), isCorriged=challenge.isCorriged, canCorrige=challenge.data is not None) + return Challenges( + name=challenge.challenger.user.username if challenge.challenger.user is not None else challenge.challenger.anonymous.username, + value=Note(note=challenge.note, time=challenge.time), isCorriged=challenge.isCorriged, + canCorrige=challenge.data is not None) -def serialize_parcours_short(parcours: Parcours, member: Member, db: Session): - if member.is_member == False: - challenges = db.exec(select(Challenge).where(Challenge.parcours_id == parcours.id, Challenge.challenger_id == member.id)).all() - else: - challenges = db.exec(select(Challenge).where( - Challenge.parcours_id == parcours.id)).all() - - challenges = [serialize_challenge(c) for c in challenges] - return Parcours(**parcours.dict(), challenges=challenges) - - def serialize_room(room: Room, member: Member, db: Session): - return RoomInfo(**room.dict(), parcours=[serialize_parcours_short(p, member, db) for p in room.parcours], members=[serialize_member(m) for m in room.members]) + return RoomInfo(**room.dict(), parcours=[serialize_parcours_short(p, member, db) for p in room.parcours], + members=[serialize_member(m, admin=member.is_admin, m2=member) for m in room.members]) + + +def getUsername(m: Member): + return m.user.username if m.user is not None else m.anonymous.username + + +def getChallengerInfo(c: Challenge, db: Session): + challenger = db.exec(select(Challenger).where(Challenger.member_id == + c.challenger_mid, Challenger.parcours_id == c.challenger_pid)).first() + if challenger is not None: + member = challenger.member + return {"name": getUsername(member), "id_code": member.id_code} + + +def getChallenges(c: Challenger, db: Session): + challenges = db.exec(select(Challenge).where(Challenge.challenger_mid == c.member_id, + Challenge.challenger_pid == c.parcours_id)).all() + return challenges + + +def getTops(p: Parcours, db: Session): + tops = db.exec(select(Challenge).where(Challenge.parcours_id == p.id_code).order_by( + col(Challenge.mistakes), col(Challenge.time)).limit(3)).all() + + tops = [{"challenger": getChallengerInfo( + t, db), "mistakes": t.mistakes, "time": t.time} for t in tops] + return tops + + +def getAvgTops(p: Parcours, db: Session): + avgTop = db.exec(select(Challenger).where(Challenger.parcours_id == + p.id).order_by(col(Challenger.avg)).limit(3)).all() + + avgTop = [{"id_code": t.member.id_code, "avg": t.avg, + "name": getUsername(t.member)} for t in avgTop] + return avgTop + + +def getRank(c: Challenger, p: Parcours, db: Session): + noteRank = db.exec(select([func.count(Challenge.id)]).where(Challenge.parcours_id == p.id_code).order_by( + col(Challenge.mistakes), col(Challenge.time)).where(Challenge.mistakes <= c.best, + Challenge.time < c.best_time)).one() + return noteRank + 1 + + +def getAvgRank(c: Challenger, p: Parcours, db: Session): + avgRank = db.exec(select([func.count(Challenger.member_id)]).where( + Challenger.parcours_id == p.id).order_by(col(Challenger.avg)).where(Challenger.avg < c.avg)).one() + return avgRank + 1 + + +def getMemberRank(m: Member, p: Parcours, db: Session): + challenger = db.exec(select(Challenger).where(Challenger.member_id == m.id)).first() + if challenger is None or challenger.best is None: + return None + return getRank(challenger, p, db) + + +def getMemberAvgRank(m: Member, p: Parcours, db: Session): + challenger = db.exec(select(Challenger).where(Challenger.member_id == m.id)).first() + print('CHALLE', challenger) + if challenger is None or challenger.avg is None: + return None + return getAvgRank(challenger, p, db) + + +def serialize_parcours(parcours: Parcours, member: Member, db: Session): + tops = getTops(parcours, db) + avgTop = getAvgTops(parcours, db) + + challenger = db.exec(select(Challenger).where( + Challenger.member_id == member.id, Challenger.parcours_id == parcours.id)).first() + + noteRank = None + avgRank = None + pb = None + if challenger is not None and challenger.avg is not None and challenger.best is not None: + noteRank = getRank(challenger, parcours, db) + avgRank = getAvgRank(challenger, parcours, db) + pb = {"mistakes": challenger.best, "time": challenger.best_time} + + statement = select(Challenger).where(Challenger.parcours_id == parcours.id) + if not member.is_admin: + statement = statement.where(Challenger.member_id == member.id) + + challengers = db.exec(statement).all() + + challs = {c.member.id_code: { + "challenger": {"id_code": c.member.id_code, "name": getUsername(c.member)}, + # 'validated': chall.mistakes <= parcours.max_mistakes + "challenges": [Challenges(**{**chall.dict(), "canCorrige": chall.data != []}) for chall in getChallenges(c, db)] + } for c in challengers} + + return {**parcours.dict(), "pb": pb, "tops": tops, "challenges": challs, "rank": noteRank, "memberRank": avgRank, + "validated": challenger.validated if challenger != None else False, "ranking": avgTop} + tops = [] + challs = {} + challenges = sorted(parcours.challenges, key=lambda x: ( + x.note['value'], x.time), reverse=True) + memberRank = None + rank = None + pb = None + validated = False + + total = 0 + + for i, chall in enumerate(challenges): + total += chall.note['value'] + id = chall.challenger.id_code + name = chall.challenger.user.username if chall.challenger.user_id != None else chall.challenger.anonymous.username + if i <= 2: + tops.append({"challenger": {"id_code": id, "name": name}, + "note": chall.note, "time": chall.time}) + + if id == member.id_code: + if challs.get(id) is None: + rank = i + 1 + memberRank = len(challs) + 1 + pb = {"note": chall.note, "time": chall.time} + if validated is False and chall.validated: + validated = True + + if member.is_admin or chall.challenger.id_code == member.id_code: + t = challs.get(id, {"total": 0})['total'] + challs[id] = {"challenger": {"id_code": id, "name": name + }, "challenges": [*challs.get(id, {'challenges': []})['challenges'], + Challenges( + **{**chall.dict(), "canCorrige": chall.data != []})], + "total": t + chall.note['value']} + + topMembers = [{**c['challenger'], "avg": c['total'] / + len(c['challenges'])} for id, c in challs.items()] + topMembers.sort(key=lambda x: x['avg'], reverse=True) + return {**parcours.dict(), "tops": tops, "challenges": challs, "rank": rank, "memberRank": memberRank, "pb": pb, + "validated": validated, + 'avg': None if len(parcours.challenges) == 0 else round(total / len(parcours.challenges), 2), + "ranking": topMembers} def change_anonymous_clientId(anonymous: Anonymous, db: Session): @@ -298,20 +452,49 @@ def change_anonymous_clientId(anonymous: Anonymous, db: Session): return anonymous -#Parcours -def validate_exercices(exos: List[ExercicesCreate], db: Session ): - exercices = db.exec(select(Exercice).where(Exercice.web == True).where(col(Exercice.id_code).in_([e.exercice_id for e in exos]))).all() - exos_id_list = [e.exercice_id for e in exos] - exercices.sort(key=lambda e: exos_id_list.index(e.id_code)) - return [Exercices(exercice_id=e.id_code, name=e.name, quantity=[ex for ex in exos if ex.exercice_id == e.id_code][0].quantity).dict() for e in exercices] +# Parcours +from services.io import add_fast_api_root +from generateur.generateur_main import generate_from_path, parseGeneratorOut -def create_parcours_db(parcours: ParcoursCreate,room_id: int, db: Session): + +def countInput(ex: Exercice, q: int): + exo = parseGeneratorOut(generate_from_path(add_fast_api_root( + ex.exo_source), 1, "web")) + return len(exo.inputs) * q + + +class ExoToCount(BaseModel): + ex: Exercice + q: int + + +def getTotal(exs: list[ExoToCount]): + total = 0 + for e in exs: + total += countInput(e.ex, e.q) + return total + + +def validate_exercices(exos: List[ExercicesCreate], db: Session): + exercices = db.exec(select(Exercice).where(Exercice.web == True).where( + col(Exercice.id_code).in_([e.exercice_id for e in exos]))).all() + exos_id_list = [e.exercice_id for e in exos] + # exoToCountList = [ExoToCount(ex=e, q=q) for e, q in zip(exercices, [c.quantity for c in exos])] + exercices.sort(key=lambda e: exos_id_list.index(e.id_code)) + return [Exercices(exercice_id=e.id_code, name=e.name, + quantity=[ex for ex in exos if ex.exercice_id == e.id_code][0].quantity, + examples=e.examples).dict() for e in exercices] + + +def create_parcours_db(parcours: ParcoursCreate, room_id: int, db: Session): exercices = validate_exercices(parcours.exercices, db) if len(exercices) == 0: return "Veuillez entrer au moins un exercice valide" id_code = generate_unique_code(Parcours, s=db) - parcours_obj = Parcours(**{**parcours.dict(), "exercices": exercices}, room_id=room_id, id_code=id_code) - print(parcours_obj) + + parcours_obj = Parcours( + **{**parcours.dict(), "exercices": exercices}, room_id=room_id, id_code=id_code) + db.add(parcours_obj) db.commit() db.refresh(parcours_obj) @@ -320,24 +503,80 @@ def create_parcours_db(parcours: ParcoursCreate,room_id: int, db: Session): def deleteParcoursRelated(parcours: Parcours, db: Session): db.exec(delete(Challenge).where(Challenge.parcours_id == parcours.id_code)) - db.exec(delete(TmpCorrection).where(TmpCorrection.parcours_id == parcours.id_code)) + db.exec(delete(TmpCorrection).where( + TmpCorrection.parcours_id == parcours.id_code)) + db.exec(delete(Challenger).where(Challenger.parcours_id == parcours.id)) + db.commit() - + + +def change_challengers_validation(p: Parcours, validation: int, db: Session): + challengers = db.exec(select(Challenger).where( + Challenger.parcours_id == p.id)).all() + challs = [] + for c in challengers: + validated = c.best <= validation + if validated != c.validated: + c.validated = validated + challs.append(c) + + db.bulk_save_objects(challs) + db.commit() + + +def change_challenges_validation(p: Parcours, validation: int, db: Session): + print('cHANGE') + challenges = db.exec(select(Challenge).where( + Challenge.parcours_id == p.id_code)).all() + print('CHALLS', challenges) + challs = [] + for c in challenges: + validated = c.mistakes <= validation + print('CHAL', validated, c.validated, c) + if validated != c.validated: + c.validated = validated + challs.append(c) + + db.bulk_save_objects(challs) + db.commit() + + +def changeValidation(p: Parcours, validation: int, db: Session): + change_challengers_validation(p, validation, db) + change_challenges_validation(p, validation, db) + + +def compareExercices(old: list[Exercices], new: list[ExercicesCreate]): + old = [{"id": o['exercice_id'], "q": o['quantity']} for o in old] + new = [{"id": n.exercice_id, "q": n.quantity} for n in new] + return old == new + + def update_parcours_db(parcours: ParcoursCreate, parcours_obj: Parcours, db: Session): - exercices = validate_exercices(parcours.exercices, db) - if len(exercices) == 0: - return "Veuillez entrer au moins un exercice valide" - - parcours_data = parcours.dict(exclude_unset=True) - for key, value in parcours_data.items(): - setattr(parcours_obj, key, value) - parcours_obj.exercices = exercices + update_challenges = False + + if not compareExercices(parcours_obj.exercices, parcours.exercices): + exercices = validate_exercices(parcours.exercices, db) + if len(exercices) == 0: + return "Veuillez entrer au moins un exercice valide" + deleteParcoursRelated(parcours_obj, db) + update_challenges = True + parcours_obj.exercices = exercices + + if parcours_obj.max_mistakes != parcours.max_mistakes: + changeValidation(parcours_obj, parcours.max_mistakes, db) + + parcours_obj.name = parcours.name + parcours_obj.time = parcours.time + parcours_obj.max_mistakes = parcours.max_mistakes + db.add(parcours_obj) db.commit() - deleteParcoursRelated(parcours_obj, db) + db.refresh(parcours_obj) - - return parcours_obj + + return parcours_obj, update_challenges + def delete_parcours_db(parcours: Parcours, db: Session): db.delete(parcours) @@ -347,138 +586,210 @@ def delete_parcours_db(parcours: Parcours, db: Session): class CorrigedChallenge(BaseModel): data: List[List[CorrigedGeneratorOut]] - note: Note + mistakes: int isCorriged: bool -def create_tmp_correction(data: List[List[CorrigedGeneratorOut]], parcours_id: str, member: Member, db: Session): +def create_tmp_correction(data: List[CorrigedData], parcours_id: str, member: Member, db: Session): code = generate_unique_code(TmpCorrection, s=db) tmpCorr = TmpCorrection(data=data, id_code=code, member=member, parcours_id=parcours_id) db.add(tmpCorr) db.commit() db.refresh(tmpCorr) + return tmpCorr -def change_challenge(challenge: Challenge, corriged: CorrigedChallenge, db: Session): - challenge.data = corriged['data'] - challenge.note = corriged['note'] - challenge.isCorriged = corriged['isCorriged'] - challenge.validated = noteOn20( - corriged['note']['value'], corriged['note']['total']) > challenge.parcours.validate_condition - db.add(challenge) - db.commit() - db.refresh(challenge) - - return challenge - - -def validate_challenge_input(obj: List[List[ParsedGeneratorOut]], corr: TmpCorrection): +def validate_challenge_input(obj: List[CorrectionData], corr: TmpCorrection): data = corr.data if len(obj) != len(data): return False for i in range(len(data)): exo_corr = data[i] exo = obj[i] - if len(exo) != len(exo_corr): + print('EXO', exo) + print('EXO', exo.data) + if len(exo.data) != len(exo_corr['data']): return - zipped = zip(exo_corr, exo) - same = all([e['calcul'] == f.calcul and len(e['inputs']) == len(f.inputs) for e,f in zipped]) + zipped = zip(exo_corr['data'], exo.data) + same = all([e['calcul'] == f.calcul and len(e['inputs']) + == len(f.inputs) for e, f in zipped]) if not same: return False return True -def validate_challenge_correction(obj: List[List[CorrigedGeneratorOut]], chall: Challenge): + +def validate_challenge_correction(obj: List[CorrigedData], chall: Challenge): data = chall.data if len(obj) != len(data): return False for i in range(len(data)): exo_corr = data[i] exo = obj[i] - if len(exo) != len(exo_corr): + if len(exo.data) != len(exo_corr['data']): return - zipped = zip(exo_corr, exo) - same = all([e['calcul'] == f.calcul and len(e['inputs']) == len(f.inputs) for e,f in zipped]) + zipped = zip(exo_corr['data'], exo.data) + same = all([e['calcul'] == f.calcul and len(e['inputs']) + == len(f.inputs) for e, f in zipped]) if not same: return False return True - def corrige_challenge(obj: List[List[ParsedGeneratorOut]], corr: TmpCorrection) -> CorrigedChallenge: - if validate_challenge_input(obj , corr) is False: + if validate_challenge_input(obj, corr) is False: return None + data = corr.data note = 0 total = 0 isCorriged = True + mistakes = 0 for i in range(len(data)): - exo_corr = data[i] - exo = obj[i] + exo_corr = data[i]["data"] + exo = obj[i].data if len(exo) != len(exo_corr): return zipped = zip(exo_corr, exo) for e, f in zipped: + print("HO\n\n") for k, l in zip(e['inputs'], f.inputs): k["value"] = str(l.value) total += 1 if k['correction'] is None: isCorriged = False - if str(k["correction"]) == str(l.value): + k['valid'] = None + + elif str(k["correction"]) == str(l.value): + k['valid'] = True note += 1 - - return {"data": data, "note": {"value": 1, "total": 3}, "isCorriged": isCorriged} - - - -def change_correction(obj: List[List[CorrigedGeneratorOut]], chall: Challenge) -> CorrigedChallenge: + else: + k['valid'] = False + mistakes += 1 + + return {"data": data, "mistakes": mistakes, "isCorriged": isCorriged} + return {"data": data, "mistakes": mistakes, "note": {"value": note, "total": total}, "isCorriged": isCorriged} + + +def change_correction(obj: List[CorrigedData], chall: Challenge) -> CorrigedChallenge: if validate_challenge_correction(obj, chall) is False: return None data = deepcopy(chall.data) note = 0 total = 0 isCorriged = True + mistakes = 0 for i in range(len(data)): - exo_corr = data[i] - exo = obj[i] + exo_corr = data[i]['data'] + exo = obj[i].data if len(exo) != len(exo_corr): return zipped = zip(exo_corr, exo) for e, f in zipped: for k, l in zip(e['inputs'], f.inputs): - k["correction"] = str(l.correction) + k["correction"] = l.correction + k["valid"] = l.valid total += 1 - if k['correction'] is None: + if k['correction'] is None and l.valid is None: isCorriged = False - if str(k["correction"]) == str(l.value): + if l.valid is True: note += 1 - + else: + mistakes += 1 + + return {"data": data, "mistakes": mistakes, "isCorriged": isCorriged} return {"data": data, "note": {"value": note, "total": total}, "isCorriged": isCorriged} - +def getChallenger(parcours: Parcours, member: Member, db: Session): + challenger = db.exec(select(Challenger).where( + Challenger.member_id == member.id, Challenger.parcours_id == parcours.id)).first() + if challenger is None: + return Challenger(parcours_id=parcours.id, member_id=member.id) + return challenger -def create_challenge(data: List[List[CorrigedGeneratorOut]], challenger: Member, parcours: Parcours, time: int, note: Note, isCorriged: bool, db: Session): - challenge = Challenge(data=data, challenger=challenger, parcours=parcours, time=time, note=note, validated=noteOn20(note["value"], note['total']) >= - parcours.validate_condition, isCorriged=isCorriged, id_code=generate_unique_code(Challenge, s=db)) + +def ChallengerFromChallenge(c: Challenge, db: Session): + challenger = db.exec(select(Challenger).where( + Challenger.member_id == c.challenger_mid, Challenger.parcours_id == c.challenger_pid)).first() + return challenger + + +def checkValidated(challenger: Challenger, db: Session, challenge: Challenge | None = None): + challenges = db.exec(select(Challenge).where(Challenge.challenger_mid == challenger.member_id, + Challenge.challenger_pid == challenger.parcours_id, + Challenge.validated == True)).all() + if challenge is not None: + challenges = [c for c in challenges if c.id != challenge.id] + return len(challenges) != 0 + + +def create_challenge(data: List[CorrigedData], challenger: Member, parcours: Parcours, time: int, mistakes: int, + isCorriged: bool, db: Session): + challenger_obj: Challenger = getChallenger(parcours, challenger, db) + validated = mistakes <= parcours.max_mistakes + challenge = Challenge(data=data, challenger_pid=challenger_obj.parcours_id, challenger_mid=challenger_obj.member_id, + parcours=parcours, time=time, mistakes=mistakes, isCorriged=isCorriged, + id_code=generate_unique_code(Challenge, s=db), validated=validated) + + if (challenger_obj.best is not None and challenger_obj.best > mistakes) or challenger_obj.best is None: + challenger_obj.best = mistakes + challenger_obj.best_time = time + + challenges = db.exec(select([func.count(Challenge.id)]).where( + Challenge.challenger_mid == challenger_obj.member_id, Challenge.challenger_pid == parcours.id)).one() + + if validated and challenger_obj.validated is False: + challenger_obj.validated = True + + avg = challenger_obj.avg + if avg is None: + avg = 0 + challenger_obj.avg = (avg * + (challenges - 1) + mistakes) / (challenges) db.add(challenge) + db.add(challenger_obj) db.commit() db.refresh(challenge) - return challenge + db.refresh(challenger_obj) + print('RETURN,', challenge, challenger_obj) + return challenge, challenger_obj + def change_challenge(challenge: Challenge, corriged: CorrigedChallenge, db: Session): + challenger = ChallengerFromChallenge(challenge, db) + + challengesCount = len(getChallenges(challenger, db)) + avg = challenger.avg * challengesCount - challenge.mistakes + parcours = challenge.parcours + if challenger.best > corriged['mistakes']: + challenger.best = corriged['mistakes'] + challenger.best_time = challenge.time + + validated = corriged['mistakes'] <= parcours.max_mistakes + challenge.validated = validated + + if challenger.validated == False and validated: + challenger.validated = True + + elif challenger.validated == True and not validated: + challenger.validated = checkValidated(challenger, db, challenge) + + challenger.avg = (avg + corriged['mistakes']) / challengesCount + challenge.data = corriged['data'] - challenge.note = corriged['note'] + challenge.mistakes = corriged['mistakes'] challenge.isCorriged = corriged['isCorriged'] - challenge.validated = noteOn20( - corriged['note']['value'], corriged['note']['total']) > challenge.parcours.validate_condition + # challenge.validated = corriged['mistakes'] <= parcours.max_mistakes db.add(challenge) + db.add(challenger) db.commit() db.refresh(challenge) + db.refresh(challenger) - return challenge + return challenge, challenger # Dependencies @@ -489,8 +800,6 @@ def check_room(room_id: str, db: Session = Depends(get_session)): return room - - def get_room(room_id, db: Session = Depends(get_session)): room = db.exec(select(Room).where(Room.id_code == room_id)).first() if room is None: @@ -499,13 +808,14 @@ def get_room(room_id, db: Session = Depends(get_session)): return room -def get_member_dep(room: Room = Depends(get_room), user: User = Depends(get_current_user_optional), clientId: str | None = Body(default=None), db: Session = Depends(get_session)): +def get_member_dep(room: Room = Depends(get_room), user: User = Depends(get_current_user_optional), + clientId: str | None = Query(default=None), db: Session = Depends(get_session)): if user is None and clientId is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated") if user is not None: member = get_member_from_user(user.id, room.id, db) - if clientId is not None: + elif clientId is not None: member = get_member_from_clientId(clientId, room.id, db) if member is None: @@ -550,11 +860,9 @@ def get_correction(correction_id: str, parcours_id: str, member: Member = Depend return tmpCorr - -def get_challenge(challenge_id: str, parcours_id: str, db: Session = Depends(get_session)): - +def get_challenge(challenge_id: str, db: Session = Depends(get_session)): challenge = db.exec(select(Challenge).where( - Challenge.id_code == challenge_id, Challenge.parcours_id == parcours_id)).first() + Challenge.id_code == challenge_id)).first() if challenge is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Challenge introuvable") @@ -564,4 +872,3 @@ def get_challenge(challenge_id: str, parcours_id: str, db: Session = Depends(get status_code=status.HTTP_400_BAD_REQUEST, detail="Impossible de corriger ce challenge") return challenge - diff --git a/backend/api/database/room/models.py b/backend/api/database/room/models.py index 5f2cd5b..0685819 100644 --- a/backend/api/database/room/models.py +++ b/backend/api/database/room/models.py @@ -2,214 +2,325 @@ from uuid import UUID, uuid4 from pydantic import root_validator, BaseModel import pydantic.json from typing import List, Optional, TYPE_CHECKING -from sqlmodel import SQLModel, Field, Relationship, JSON, Column - +from sqlmodel import SQLModel, Field, Relationship, JSON, Column, ForeignKeyConstraint +from database.exercices.models import Example from database.auth.models import UserRead + if TYPE_CHECKING: from database.auth.models import User - - + + class RoomBase(SQLModel): - name: str = Field(max_length=20) - public: bool = Field(default=False) - global_results: bool = Field(default=False) + name: str = Field(max_length=20) + public: bool = Field(default=False) + global_results: bool = Field(default=False) + class RoomCreate(RoomBase): - pass + pass + class Room(RoomBase, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - id_code: str = Field(index=True) - - members: List['Member'] = Relationship(back_populates="room") - parcours: List['Parcours'] = Relationship(back_populates="room") - - + id: Optional[int] = Field(default=None, primary_key=True) + id_code: str = Field(index=True) + + members: List['Member'] = Relationship(back_populates="room") + parcours: List['Parcours'] = Relationship(back_populates="room") + + class AnonymousBase(SQLModel): - username: str = Field(max_length=20) - + username: str = Field(max_length=20) + + class AnonymousCreate(AnonymousBase): - pass + pass + class Anonymous(AnonymousBase, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - reconnect_code: str = Field(index=True) - - clientId: Optional[UUID] = Field(default=uuid4(), index=True) - member: 'Member' = Relationship(back_populates="anonymous") - - -class Member(SQLModel, table = True): - id: Optional[int] = Field(default=None, primary_key=True) - id_code: str = Field(index=True) - - user_id: Optional[int] = Field(foreign_key="user.id", default=None) - user: Optional["User"] = Relationship(back_populates='members') - - anonymous_id: Optional[int] = Field(foreign_key="anonymous.id", default=None) - anonymous: Optional[Anonymous] = Relationship(back_populates="member") - - room_id: int = Field(foreign_key="room.id") - room: Room = Relationship(back_populates='members') + id: Optional[int] = Field(default=None, primary_key=True) + reconnect_code: str = Field(index=True) + + clientId: Optional[UUID] = Field(default=uuid4(), index=True) + member: 'Member' = Relationship(back_populates="anonymous") + + +class Member(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + id_code: str = Field(index=True) + + user_id: Optional[int] = Field(foreign_key="user.id", default=None) + user: Optional["User"] = Relationship(back_populates='members') + + anonymous_id: Optional[int] = Field( + foreign_key="anonymous.id", default=None) + anonymous: Optional[Anonymous] = Relationship(back_populates="member") + + room_id: int = Field(foreign_key="room.id") + room: Room = Relationship(back_populates='members') + + challengers: List["Challenger"] = Relationship(back_populates="member") + + is_admin: bool = False + + waiting: bool = False + + online: bool = False + + waiter_code: Optional[str] = Field(default=None) + + corrections: List['TmpCorrection'] = Relationship(back_populates="member") - challenges: List["Challenge"] = Relationship(back_populates="challenger") - - is_admin: bool = False - - waiting: bool = False - online: bool = False - - waiter_code: Optional[str] = Field(default= None) - - corrections: List['TmpCorrection'] = Relationship(back_populates="member") - class ExercicesCreate(SQLModel): - exercice_id: str - quantity: int = 10 - -class Exercices(ExercicesCreate): - name: str + exercice_id: str + quantity: int = 10 + +class Exercices(ExercicesCreate): + name: str + examples: Example + +class Challenger(SQLModel, table=True): + member_id: int = Field(foreign_key="member.id", primary_key=True) + parcours_id: int = Field(foreign_key="parcours.id", primary_key=True) + + parcours: "Parcours" = Relationship(back_populates="challengers") + member: "Member" = Relationship(back_populates="challengers") + + #challenges: list["Challenge"] = Relationship(back_populates="challenger") + + avg: Optional[float] = Field(default=None) + best: Optional[int] = Field(default=None) + best_time: Optional[int] = Field(default=None) + validated: bool = Field(default=False) + class Parcours(SQLModel, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - id_code: str = Field(index=True, unique=True) - - room_id: int = Field(foreign_key="room.id") - room: Room = Relationship(back_populates='parcours') - - name: str - time: int - validate_condition: int - - exercices: List[Exercices] = Field(sa_column=Column(JSON)) - challenges: List["Challenge"] = Relationship(back_populates="parcours") - corrections: List["TmpCorrection"] = Relationship(back_populates="parcours") + id: Optional[int] = Field(default=None, primary_key=True) + id_code: str = Field(index=True, unique=True) + + room_id: int = Field(foreign_key="room.id") + room: Room = Relationship(back_populates='parcours') + + challengers: list[Challenger] = Relationship(back_populates="parcours") + + name: str + time: int + + + max_mistakes: int + + exercices: List[Exercices] = Field(sa_column=Column(JSON)) + challenges: List["Challenge"] = Relationship(back_populates="parcours") + corrections: List["TmpCorrection"] = Relationship( + back_populates="parcours") + + + class Note(BaseModel): - value: int - total: int - - + value: int + total: int + + class TimedNote(Note): - time: int - + time: int + + class ParcoursReadShort(SQLModel): - name: str - best_note: str | None = None - id_code: str - + name: str + best_note: str | None = None + validated: bool = False + id_code: str + +class ParcoursReadUpdate(SQLModel): + id_code: str + name: str + time: int + max_mistakes: int + exercices: List[Exercices] + update_challenges: bool = False + +class ChallengerInfo(BaseModel): + name: str + id_code: str + + +class ChallengerAverage(ChallengerInfo): + avg: float + + class Challenges(SQLModel): - id_code: str - challenger: str - note: Note - time: int - isCorriged: bool - canCorrige: bool - validated: bool + id_code: str + mistakes: int + time: int + isCorriged: bool + canCorrige: bool = True + validated: bool = False + + +class ChallengeInfo(BaseModel): + challenger: ChallengerInfo + challenges: list[Challenges] + #total: int + +class Tops(BaseModel): + challenger: ChallengerInfo + mistakes: int + time: int class ParcoursRead(SQLModel): - name: str - time: int - validate_condition: int - id_code: str - exercices: List[Exercices] - challenges: List[Challenges] - + tops: list[Tops] + name: str + time: int + max_mistakes: int + id_code: str + exercices: List[Exercices] + challenges: dict[str, ChallengeInfo] + rank: int | None + pb: dict[str, int] | None + memberRank: int | None + validated: bool + ranking: list[ChallengerAverage] + #avg: float | None + + class ParcoursCreate(SQLModel): - name: str - time: int - validate_condition: int - exercices: List[ExercicesCreate] + name: str + time: int + max_mistakes: int + exercices: List[ExercicesCreate] class NotCorrigedInput(BaseModel): - index: int - value: str - + index: int + value: str + + class CorrigedInput(NotCorrigedInput): - correction: str + correction: str | None + valid: bool | None + class ParsedGeneratorOut(BaseModel): - calcul: str - inputs: List[NotCorrigedInput] - + calcul: str + inputs: List[NotCorrigedInput] + + class CorrigedGeneratorOut(BaseModel): - calcul: str - inputs: List[CorrigedInput] + calcul: str + inputs: List[CorrigedInput] + class Challenge(SQLModel, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - id_code: str = Field(index=True, unique=True) - - challenger_id: int = Field(foreign_key="member.id") - challenger: Member = Relationship(back_populates="challenges") - - parcours_id: int = Field(foreign_key="parcours.id_code") - parcours: Parcours = Relationship(back_populates="challenges") - - data: Optional[List[List[CorrigedGeneratorOut]]] = Field(sa_column=Column(JSON), default=[]) - - time: int - note: Note = Field( - sa_column=Column(JSON)) - validated: bool - isCorriged: bool - - + id: Optional[int] = Field(default=None, primary_key=True) + id_code: str = Field(index=True, unique=True) + + ''' challenger_id: int = Field(foreign_key="member.id") + challenger: Member = Relationship(back_populates="challenges") ''' + + parcours_id: int = Field(foreign_key="parcours.id_code") + parcours: Parcours = Relationship(back_populates="challenges") + + challenger_pid: int + challenger_mid: int + __table_args__ = (ForeignKeyConstraint(["challenger_pid", "challenger_mid"], + ["challenger.parcours_id","challenger.member_id"]), + {}) + + #challenger: "Challenger" = Relationship(back_populates="challenges") + + data: Optional[List] = Field( + sa_column=Column(JSON), default=[]) + + time: int + ''' note: Note = Field( + sa_column=Column(JSON)) ''' + + mistakes: int + #note_value: int + validated: bool + + isCorriged: bool + + +class ExoInfo(BaseModel): + id_code: str + name: str + consigne: str | None + + +class CorrigedData(BaseModel): + exo: ExoInfo + data: List[CorrigedGeneratorOut] + + +class CorrectionData(BaseModel): + exo: ExoInfo + data: List[ParsedGeneratorOut] + + class ChallengeRead(SQLModel): id_code: str - data: Optional[List[List[CorrigedGeneratorOut]]] = [] + data: Optional[List[CorrigedData]] = [] time: int - note: Note + mistakes: int validated: bool isCorriged: bool + class TmpCorrection(SQLModel, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - id_code: str = Field(index=True) - - parcours_id: str = Field(foreign_key="parcours.id_code") - parcours: Parcours = Relationship(back_populates="corrections") - - member_id: int = Field(foreign_key="member.id") - member: Member = Relationship(back_populates="corrections") - - data: List[List[CorrigedGeneratorOut]] = Field(sa_column=Column(JSON)) - + id: Optional[int] = Field(default=None, primary_key=True) + id_code: str = Field(index=True) + + parcours_id: str = Field(foreign_key="parcours.id_code") + parcours: Parcours = Relationship(back_populates="corrections") + + member_id: int = Field(foreign_key="member.id") + member: Member = Relationship(back_populates="corrections") + + data: List = Field(sa_column=Column(JSON)) + + class AnonymousRead(AnonymousBase): - reconnect_code: str - + reconnect_code: str + + class Username(SQLModel): - username: str - - + username: str + + class MemberRead(SQLModel): - username: str - reconnect_code: str = '' - isUser: bool - isAdmin: bool - id_code: str + username: str + reconnect_code: str = '' + isUser: bool + isAdmin: bool + id_code: str + clientId: str = "" + online: bool class RoomRead(RoomBase): - id_code: str - + id_code: str + + class RoomAndMember(BaseModel): room: RoomRead member: MemberRead -class RoomInfo(RoomRead): - public: bool - name: str - members: List[MemberRead] - parcours: List[ParcoursReadShort] - + class Waiter(BaseModel): username: str waiter_id: str + +class RoomInfo(RoomRead): + public: bool + name: str + members: List[MemberRead | Waiter] + parcours: List[ParcoursReadShort] + + class RoomConnectionInfos(BaseModel): - room: str - member: str | None = None \ No newline at end of file + room: str + member: str | None = None diff --git a/backend/api/database2.db b/backend/api/database2.db new file mode 100644 index 0000000000000000000000000000000000000000..74c4bbf24b9cceab1ca925fa7575ad4fe0f6a014 GIT binary patch literal 724992 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 literal 0 HcmV?d00001 diff --git a/backend/api/database3.db b/backend/api/database3.db new file mode 100644 index 0000000000000000000000000000000000000000..004a8964398da46600d4702e06d0b8350dea2ec8 GIT binary patch literal 143360 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 literal 0 HcmV?d00001 diff --git a/backend/api/database6.db b/backend/api/database6.db new file mode 100644 index 0000000000000000000000000000000000000000..470d7882a33ca7f2d51888e319759e9fb683c73e GIT binary patch literal 159744 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 TL-b$sqN7kGvRWB*?6LnJf+WE+ literal 0 HcmV?d00001 diff --git a/backend/api/database7.db b/backend/api/database7.db new file mode 100644 index 0000000000000000000000000000000000000000..fbf27af8026a58759d1aae53c37cf6e173e0f0b9 GIT binary patch literal 135168 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 15: + await self.send_error("Nom d'utilisateur invalide ou indisponible") + return anonymous = create_anonymous(username, self.room, self.db) if anonymous is None: await self.send_error("Nom d'utilisateur invalide ou indisponible") @@ -134,61 +161,69 @@ class RoomConsumer(Consumer): waiter = create_member( anonymous=anonymous, room=self.room, waiting=self.room.public is False, db=self.db) - + self.member = waiter self.add_to_group() - + if self.room.public is False: await self.direct_send(type="waiting", payload={"waiter": serialize_member(self.member)}) await self.send_to_admin(type="waiter", payload={"waiter": serialize_member(self.member)}) else: - await self.broadcast(type="joined", payload={"member": serialize_member(self.member)}, exclude=True) - await self.direct_send(type="accepted", payload={"member": serialize_member(self.member)}) - + await self.manager.broadcast( + lambda m: {"type": "joined", "data": {"member": serialize_member(self.member, admin=m.is_admin, m2=m)}}, + self.room.id_code) + # await self.broadcast(type="joined", payload={"member": serialize_member(self.member)}, exclude=True) + await self.direct_send(type="accepted", + payload={"member": serialize_member(self.member, private=True, m2=self.member)}) @Consumer.event('accept', conditions=[isAdminReceive]) async def accept(self, waiter_id: str): waiter = get_waiter(waiter_id, self.db) if waiter is None: - await self.send_error("Utilisateur en list d'attente introuvable") + await self.send_error("Utilisateur en liste d'attente introuvable") return member = accept_waiter(waiter, self.db) - await self.send_to(type="accepted", payload={"member": serialize_member(member)}, member_id=waiter_id) - await self.broadcast(type="joined", payload={"member": serialize_member(member)}) + await self.send_to(type="accepted", payload={"member": serialize_member(member, private=True, m2=member)}, + member_id=waiter_id) + await self.manager.broadcast( + lambda m: {"type": "joined", + "data": {"member": serialize_member(member, admin=m.is_admin, m2=m)}}, + self.room.id_code) + # await self.broadcast(type="joined", payload={"member": serialize_member(member)}) @Consumer.event('refuse', conditions=[isAdminReceive]) - async def accept(self, waiter_id: str): + async def refuse(self, waiter_id: str): waiter = get_waiter(waiter_id, self.db) - member = refuse_waiter(waiter, self.db) + refuse_waiter(waiter, self.db) await self.send_to(type="refused", payload={'waiter_id': waiter_id}, member_id=waiter_id) - await self.direct_send(type="successfullyRefused", payload= {"waiter_id": waiter_id}) - + await self.direct_send(type="successfullyRefused", payload={"waiter_id": waiter_id}) + @Consumer.event('ping_room') async def proom(self): await self.broadcast(type='ping', payload={}, exclude=True) - + @Consumer.event('sub_parcours') async def sub_parcours(self, parcours_id: str): if isinstance(self.member, Member) and self.member.waiting == False: self.manager.add(parcours_id, self) - + @Consumer.event('unsub_parcours') async def unsub_parcours(self, parcours_id: str): if isinstance(self.member, Member) and self.member.waiting == False: self.manager.remove(parcours_id, self) - + @Consumer.event('set_name', conditions=[isAdminReceive]) async def change_name(self, name: str): if len(name) < 20: - self.room = change_room_name(self.room,name, self.db) + self.room = change_room_name(self.room, name, self.db) print('SENDING') await self.broadcast(type="new_name", payload={"name": name}) - + return await self.send_error('Nom trop long (max 20 character)') - + @Consumer.event('set_visibility', conditions=[isAdminReceive]) - async def change_name(self, public: bool): + async def change_visibility(self, public: bool): self.room = change_room_status(self.room, public, self.db) await self.broadcast(type="new_visibility", payload={"public": public}) @@ -197,7 +232,6 @@ class RoomConsumer(Consumer): await self.direct_send(type="error", payload={"msg": "Vous n'êtes connecté à aucune salle"}) return self.member is not None - @Consumer.event('leave', conditions=[isMember]) async def leave(self): if self.member.is_admin is True: @@ -205,7 +239,7 @@ class RoomConsumer(Consumer): return member_obj = serialize_member(self.member) leave_room(self.member, self.db) - + await self.direct_send(type="successfully_leaved", payload={}) await self.broadcast(type='leaved', payload={"member": member_obj}) self.member = None @@ -219,7 +253,7 @@ class RoomConsumer(Consumer): if member.is_admin is True: await self.send_error("Vous ne pouvez pas bannir un administrateur") return - + member_serialized = serialize_member(member) leave_room(member, self.db) await self.send_to(type="banned", payload={}, member_id=member.id_code) @@ -227,10 +261,11 @@ class RoomConsumer(Consumer): # Sending Events - @Consumer.sending(['connect', "disconnect", "joined"], conditions=[isMember]) + @Consumer.sending(["joined"], conditions=[isMember]) def joined(self, member: MemberRead): + if self.member.id_code == member.id_code: - raise ValueError("") # Prevent from sending event + raise ValueError("") # Prevent from sending event if self.member.is_admin == False: member.reconnect_code = "" return {"member": member} @@ -239,6 +274,11 @@ class RoomConsumer(Consumer): def waiter(self, waiter: Waiter): return {"waiter": waiter} + @Consumer.sending('accepted') + def accepted(self, member: MemberRead): + self.db.refresh(self.member) + return {"member": member} + @Consumer.sending("refused", conditions=[isWaiter]) def refused(self, waiter_id: str): self.member = None @@ -249,6 +289,7 @@ class RoomConsumer(Consumer): def banned(self): self.member = None self.manager.remove(self.room.id, self) + self.ws.close() return {} @Consumer.sending('ping', conditions=[isMember]) @@ -256,6 +297,6 @@ class RoomConsumer(Consumer): return {} async def disconnect(self): + print('DISCONNECTED', self.member) self.manager.remove(self.room.id, self) await self.disconnect_self() - diff --git a/backend/api/routes/room/manager.py b/backend/api/routes/room/manager.py index 5545ca3..1f0dac7 100644 --- a/backend/api/routes/room/manager.py +++ b/backend/api/routes/room/manager.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING, Dict, List, Callable, Any from starlette.websockets import WebSocketState if TYPE_CHECKING: from routes.room.consumer import RoomConsumer @@ -16,7 +16,8 @@ class RoomManager: self.active_connections[group].append(member) async def _send(self, connection: "RoomConsumer", message, group: str): - if connection.ws.application_state == WebSocketState.DISCONNECTED: + print("STATE", connection.ws.client_state.__str__()) + if connection.ws.application_state == WebSocketState.DISCONNECTED or connection.ws.client_state == WebSocketState.DISCONNECTED: self.remove(group, connection) elif connection.ws.application_state == WebSocketState.CONNECTED: await connection.send(message) @@ -24,14 +25,17 @@ class RoomManager: def remove(self, group: str, member: "RoomConsumer"): if group in self.active_connections: if member in self.active_connections[group]: + print("remoied") self.active_connections[group].remove(member) - async def broadcast(self, message, group: str, exclude: list["RoomConsumer"] = []): - print('BROADCaST', message) + async def broadcast(self, message: Any | Callable, group: str, conditions: list[Callable] = [], exclude: list["RoomConsumer"] = [], ): + print('BROADCaST', message, self.active_connections) + + if group in self.active_connections: for connection in list(set(self.active_connections[group])): - # print(connection) - if connection not in exclude: + print(connection, connection.ws.state, connection.ws.client_state, connection.ws.application_state) + if connection not in exclude and all(f(connection) for f in conditions ): await self._send(connection, message, group) async def send_to(self, group, id_code, msg): @@ -44,6 +48,6 @@ class RoomManager: async def send_to_admin(self, group, msg): if group in self.active_connections: members = [c for c in self.active_connections[group] - if c.member.is_admin == True] + if c.member is not None and c.member.is_admin == True] for m in members: await self._send(m, msg, group) diff --git a/backend/api/routes/room/routes.py b/backend/api/routes/room/routes.py index 4c2cf9a..c39a2c6 100644 --- a/backend/api/routes/room/routes.py +++ b/backend/api/routes/room/routes.py @@ -1,26 +1,52 @@ -from services.database import generate_unique_code -from services.io import add_fast_api_root -from generateur.generateur_main import generate_from_path, parseGeneratorOut, parseOut -from database.exercices.models import Exercice -from database.room.crud import CorrigedChallenge, change_correction, corrige_challenge, create_parcours_db, delete_parcours_db, create_room_db, get_member_dep, check_room, serialize_room, update_parcours_db, get_parcours, get_room, check_admin, get_exercices, get_challenge, get_correction, create_tmp_correction, create_challenge, change_challenge +from typing import List, Optional -from pydantic import BaseModel -from typing import Any, Callable, Dict, List, Optional from fastapi import APIRouter, Depends, WebSocket, status, Query, Body -from config import ALGORITHM, SECRET_KEY -from database.auth.crud import get_user_from_clientId_db +from fastapi.exceptions import HTTPException +from pydantic import BaseModel +from sqlmodel import Session, select + from database.auth.models import User from database.db import get_session - -from sqlmodel import Session, col, select -from database.room.models import Challenge, ChallengeRead, Challenges, CorrigedGeneratorOut, Member, Note, Parcours, ParcoursCreate, ParcoursRead, ParcoursReadShort, ParsedGeneratorOut, Room, RoomConnectionInfos, RoomCreate, RoomAndMember, RoomInfo, TmpCorrection +from database.exercices.models import Exercice +from database.room.crud import serialize_parcours_short, change_correction, corrige_challenge, \ + create_parcours_db, delete_parcours_db, create_room_db, get_member_dep, check_room, serialize_room, \ + update_parcours_db, get_parcours, get_room, check_admin, get_exercices, get_challenge, get_correction, \ + create_tmp_correction, create_challenge, change_challenge, serialize_parcours, getTops, getAvgRank, getRank, \ + getAvgTops, ChallengerFromChallenge, getMemberAvgRank, getMemberRank +from database.room.models import Challenge, ChallengeRead, Challenges, ParcoursReadUpdate, ChallengeInfo, Member, \ + Parcours, ParcoursCreate, ParcoursRead, ParcoursReadShort, Room, RoomConnectionInfos, \ + RoomCreate, RoomInfo, TmpCorrection, CorrigedData, CorrectionData +from generateur.generateur_main import generate_from_path, parseGeneratorOut from routes.room.consumer import RoomConsumer from routes.room.manager import RoomManager from services.auth import get_current_user_optional +from services.io import add_fast_api_root +from services.misc import stripKeyDict +from typing import List, Optional + +from fastapi import APIRouter, Depends, WebSocket, status, Query, Body from fastapi.exceptions import HTTPException -from database.auth.crud import get_user_from_token -from services.websocket import Consumer -from services.misc import noteOn20, stripKeyDict +from pydantic import BaseModel +from sqlmodel import Session, select + +from database.auth.models import User +from database.db import get_session +from database.exercices.models import Exercice +from database.room.crud import serialize_parcours_short, change_correction, corrige_challenge, \ + create_parcours_db, delete_parcours_db, create_room_db, get_member_dep, check_room, serialize_room, \ + update_parcours_db, get_parcours, get_room, check_admin, get_exercices, get_challenge, get_correction, \ + create_tmp_correction, create_challenge, change_challenge, serialize_parcours, getTops, getAvgRank, getRank, \ + getAvgTops, ChallengerFromChallenge, getMemberAvgRank, getMemberRank +from database.room.models import Challenge, ChallengeRead, Challenges, ParcoursReadUpdate, ChallengeInfo, Member, \ + Parcours, ParcoursCreate, ParcoursRead, ParcoursReadShort, Room, RoomConnectionInfos, \ + RoomCreate, RoomInfo, TmpCorrection, CorrigedData, CorrectionData +from generateur.generateur_main import generate_from_path, parseGeneratorOut +from routes.room.consumer import RoomConsumer +from routes.room.manager import RoomManager +from services.auth import get_current_user_optional +from services.io import add_fast_api_root +from services.misc import stripKeyDict + router = APIRouter(tags=["room"]) manager = RoomManager() @@ -28,6 +54,7 @@ manager = RoomManager() def get_manager(): return manager + @router.post('/room', response_model=RoomConnectionInfos) def create_room(room: RoomCreate, username: Optional[str] = Query(default=None, max_length=20), user: User | None = Depends(get_current_user_optional), db: Session = Depends(get_session)): room_obj = create_room_db(room=room, user=user, username=username, db=db) @@ -39,36 +66,42 @@ def get_room_route(room: Room = Depends(get_room), member: Member = Depends(get_ return serialize_room(room, member, db) - @router.post('/room/{room_id}/parcours', response_model=ParcoursRead) async def create_parcours(*, parcours: ParcoursCreate, room_id: str, member: Member = Depends(check_admin), m: RoomManager = Depends(get_manager), db: Session = Depends(get_session)): parcours_obj = create_parcours_db(parcours, member.room_id, db) if type(parcours_obj) == str: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=parcours_obj) - await m.broadcast({"type": "add_parcours", "data": {"parcours": ParcoursReadShort(**parcours_obj.dict(exclude_unset=True)).dict()}}, room_id) - return parcours_obj + await m.broadcast({"type": "add_parcours", + "data": {"parcours": ParcoursReadShort(**parcours_obj.dict(exclude_unset=True)).dict()}}, + room_id) + + return serialize_parcours(parcours_obj, member, db) @router.get('/room/{room_id}/parcours/{parcours_id}', response_model=ParcoursRead) async def get_parcours_route(*, parcours: Parcours = Depends(get_parcours), member: Member = Depends(get_member_dep), db: Session = Depends(get_session)): - if member.is_admin == False: - return {**parcours.dict(), "challenges": [Challenges(**{**chall.dict(), "challenger": member.id_code, "canCorrige": chall.data != []}) for chall in parcours.challenges if chall.challenger.id_code == member.id_code]} - - if member.is_admin == True: - return {**parcours.dict(), "challenges": [Challenges(**{**chall.dict(), "challenger": member.id_code, "canCorrige": chall.data != []}) for chall in parcours.challenges]} + return serialize_parcours(parcours, member, db) -@router.put('/room/{room_id}/parcours/{parcours_id}', response_model=ParcoursRead, dependencies=[Depends(check_admin)]) -async def update_parcours(*, room_id: str, parcours: ParcoursCreate, parcours_old: Parcours = Depends(get_parcours), m: RoomManager = Depends(get_manager), db: Session = Depends(get_session)): - parcours_obj = update_parcours_db(parcours, parcours_old, db) +@router.put('/room/{room_id}/parcours/{parcours_id}', response_model=ParcoursRead) +async def update_parcours(*, room_id: str, parcours: ParcoursCreate, member: Member = Depends(check_admin), + parcours_old: Parcours = Depends(get_parcours), m: RoomManager = Depends(get_manager), + db: Session = Depends(get_session)): + parcours_obj, update_challenges = update_parcours_db( + parcours, parcours_old, db) if type(parcours_obj) == str: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=parcours_obj) - short = ParcoursReadShort( - **parcours_obj.dict(exclude_unset=True), id_code=parcours_obj.id_code) - await m.broadcast({"type": "update_parcours", "data": {"parcours": short.dict()}}, room_id) - return parcours_obj + + await m.broadcast(lambda m: {"type": "update_parcours", + "data": {"parcours": serialize_parcours_short(parcours_obj, m, db).dict()}}, room_id) + await m.broadcast({"type": "edit_parcours", "data": { + "parcours": ParcoursReadUpdate(**parcours_obj.dict(), update_challenges=update_challenges).dict()}}, + parcours_old.id_code) + + return serialize_parcours(parcours_obj, member, db) + return {**parcours_obj.dict()} @router.delete('/room/{room_id}/parcours/{parcours_id}', dependencies=[Depends(check_admin)]) @@ -82,51 +115,133 @@ class Exos(BaseModel): exercice: Exercice quantity: int + @router.get('/room/{room_id}/challenge/{parcours_id}') -def challenge_route(parcours_id: str, exercices: List[Exos] = Depends(get_exercices), member: Member = Depends(get_member_dep), db: Session = Depends(get_session)): +def challenge_route(parcours: Parcours = Depends(get_parcours), exercices: List[Exos] = Depends(get_exercices), + member: Member = Depends(get_member_dep), db: Session = Depends(get_session)): + print('GENERATE', exercices) correction = [parseGeneratorOut(generate_from_path(add_fast_api_root( e['exercice'].exo_source), e['quantity'], "web")) for e in exercices] - sending = [[{**c, 'inputs': [stripKeyDict(i, "correction") - for i in c['inputs']]} for c in e] for e in correction] - tmpCorr = create_tmp_correction(correction, parcours_id, member, db) - return {'challenge': sending, "id_code": tmpCorr.id_code} + infos = [{"name": e["exercice"].name, "consigne": e['exercice'].consigne, "id_code": e['exercice'].id_code} + for e in exercices] + + sending = [{"exo": ei, "data": [{**c, 'inputs': [stripKeyDict(i, "correction") + for i in c['inputs']]} for c in e]} for e, ei in + zip(correction, infos)] + + corriged = [{"exo": ei, "data": e} for e, ei in zip(correction, infos)] + tmpCorr = create_tmp_correction(corriged, parcours.id_code, member, db) + + return {'challenge': sending, "id_code": tmpCorr.id_code, + 'parcours': {"name": parcours.name, 'time': parcours.time, "max_mistakes": parcours.max_mistakes, + 'id_code': parcours.id_code}} @router.post('/room/{room_id}/challenge/{parcours_id}/{correction_id}', response_model=ChallengeRead) -async def send_challenge(*, challenge: List[List[ParsedGeneratorOut]], correction: TmpCorrection = Depends(get_correction), time: int = Body(), db: Session = Depends(get_session), m: RoomManager = Depends(get_manager),): +async def send_challenge(*, challenge: List[CorrectionData], correction: TmpCorrection = Depends(get_correction), + time: int = Body(), db: Session = Depends(get_session), + m: RoomManager = Depends(get_manager), ): parcours = correction.parcours member = correction.member data = corrige_challenge(challenge, correction) + if data is None: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail={"challenge_error":"Object does not correspond to correction"}) - chall = create_challenge(**data, challenger=member, - parcours=parcours, time=time, db=db) - - await m.broadcast({"type": "challenge", "data": Challenges(**{**chall.dict(), "challenger": member.id_code, "canCorrige": chall.data != []}).dict()}, parcours.id_code) + status_code=status.HTTP_400_BAD_REQUEST, + detail={"challenge_error": "Object does not correspond to correction"}) + chall, challenger = create_challenge(**data, challenger=member, + parcours=parcours, time=time, db=db) + print('CHALLENGE', chall) + await m.broadcast({"type": "challenge", "data": ChallengeInfo( + challenger={"name": member.user.username if member.user_id != None else member.anonymous.username, + "id_code": member.id_code}, + challenges=[Challenges(**{**chall.dict(), "canCorrige": chall.data != [], })]).dict()}, parcours.id_code, + conditions=[lambda c: c.member.is_admin or c.member.id_code == member.id_code]) + #TODO : Envoyer que à ceux d'après + await m.broadcast(lambda m: {"type": "newRanks", "data": {"rank": getMemberRank(m, correction.parcours, db), + "avgRank": getMemberAvgRank(m, correction.parcours, db)}}, + parcours.id_code) + print('CHALLENGE', chall) + rank, avgRank = getRank( + challenger, parcours, db), getAvgRank(challenger, parcours, db) + print('RANKS', rank, avgRank) + if rank <= 3 or avgRank <= 3: + await m.broadcast({"type": "newTops", "data": { + "tops": getTops(correction.parcours, db), + "avgTops": getAvgTops(correction.parcours, db), + }}, parcours.id_code) + print('CHALLENGE', chall) db.delete(correction) + returnValue = {**chall.dict(), 'validated': chall.mistakes <= correction.parcours.max_mistakes} db.commit() - return chall + return returnValue + # return {**chall.dict(), 'validated': chall.mistakes <= correction.parcours.max_mistakes} -@router.get('/room/{room_id}/challenge/{parcours_id}/{challenge_id}', response_model=ChallengeRead, dependencies=[Depends(get_member_dep)]) -async def challenge_read(*, challenge: Challenge = Depends(get_challenge)): - return challenge +class ParcoursInfo(BaseModel): + name: str + time: int + # validate: int + id_code: str -@router.put('/room/{room_id}/challenge/{parcours_id}/{challenge_id}', response_model=ChallengeRead, dependencies=[Depends(check_admin)]) -async def corrige(*, correction: List[List[CorrigedGeneratorOut]], challenge: Challenge = Depends(get_challenge), db: Session = Depends(get_session), m: RoomManager = Depends(get_manager),): +class Chall(BaseModel): + challenge: Challenge + parcours: ParcoursInfo + + +# response_model=ChallengeRead +@router.get('/room/{room_id}/correction/{challenge_id}', dependencies=[Depends(get_member_dep)]) +async def challenge_read(*, challenge: Challenge = Depends(get_challenge), db: Session = Depends(get_session)): parcours = challenge.parcours - member = challenge.challenger + + member = db.exec(select(Member).where( + Member.id == challenge.challenger_mid)).first() + challenger = ChallengerFromChallenge(challenge, db) + obj = member.user if member.user_id is not None else member.anonymous + return {**ChallengeRead(**challenge.dict()).dict(), "challenger": {"name": obj.username}, + 'parcours': {"name": parcours.name, 'time': parcours.time, "max_mistakes": parcours.max_mistakes, + 'id_code': parcours.id_code}} + + +# response_model=ChallengeRead + + +@router.put('/room/{room_id}/correction/{challenge_id}', dependencies=[Depends(check_admin)]) +async def corrige(*, correction: List[CorrigedData] = Body(), challenge: Challenge = Depends(get_challenge), + db: Session = Depends(get_session), m: RoomManager = Depends(get_manager), ): data = change_correction(correction, challenge) if data is None: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail={"correction_error": "Object does not correspond to challenge"}) - challenge = change_challenge(challenge, data, db) + status_code=status.HTTP_400_BAD_REQUEST, + detail={"correction_error": "Object does not correspond to challenge"}) - await m.broadcast({"type": "challenge_change", "data": Challenges(**{**challenge.dict(), "challenger": member.id_code, "canCorrige": challenge.data != []}).dict()}, parcours.id_code) - return challenge + parcours = challenge.parcours + member = db.exec(select(Member).where( + Member.id == challenge.challenger_mid)).first() + challenge, challenger = change_challenge(challenge, data, db) + obj = member.user if member.user_id is not None else member.anonymous + + await m.broadcast(lambda m: {"type": "newRanks", "data": {"rank": getMemberRank(m, parcours, db), + "avgRank": getMemberAvgRank(m, parcours, db)}}, + parcours.id_code) + + rank, avgRank = getRank( + challenger, parcours, db), getAvgRank(challenger, parcours, db) + print('Rank', rank, avgRank) + if rank <= 3 or avgRank <= 3: + await m.broadcast({"type": "newTops", "data": { + "tops": getTops(parcours, db), + "avgTops": getAvgTops(parcours, db), + }}, parcours.id_code) + + await m.broadcast({"type": "challenge_change", + "data": {"challenge": Challenges(**challenge.dict()).dict(), "member": member.id_code}}, + parcours.id_code, conditions=[lambda m: m.member.is_admin or m.member.id_code == member.id_code]) + + return {**ChallengeRead(**challenge.dict()).dict(), "challenger": {"name": obj.username}} + return {**ChallengeRead.from_orm(challenge).dict(), "challenger": {"name": obj.username, }} @router.websocket('/ws/room/{room_id}') diff --git a/backend/api/services/websocket.py b/backend/api/services/websocket.py index 5ce825b..bb1a220 100644 --- a/backend/api/services/websocket.py +++ b/backend/api/services/websocket.py @@ -3,6 +3,7 @@ from pydantic import validate_arguments, BaseModel from fastapi.websockets import WebSocketDisconnect, WebSocket from pydantic.error_wrappers import ValidationError import inspect +from starlette.websockets import WebSocketState def make_event_decorator(eventsDict): def _(name: str | List, conditions: List[Callable | bool] = []): @@ -68,8 +69,11 @@ class Consumer: await self.ws.send_json({"type": "error", "data": {"detail": [{ers['loc'][-1]: ers['msg']} for ers in errors]}}) async def send(self, payload): + ''' if self.ws.state == WebSocketState.DISCONNECTED: + return ''' type = payload.get('type', None) - #print('TYPE', type, self.member) + print('TYPE', type, self.member) + if type is not None: event_wrapper = self.sendings.get(type, None) if event_wrapper is not None: @@ -85,6 +89,7 @@ class Consumer: try: validated_payload = model(self=self, **data) except ValidationError as e: + print("ERROR", e) await self.ws.send_json({"type": "error", "data": {"msg": "Oops there was an error"}}) return @@ -133,4 +138,5 @@ class Consumer: data = await self.ws.receive_json() await self.receive(data) except WebSocketDisconnect: + print('DISCONNECTION') await self.disconnect() diff --git a/backend/api/tests/test_exos.py b/backend/api/tests/test_exos.py index 4536983..c9c5a88 100644 --- a/backend/api/tests/test_exos.py +++ b/backend/api/tests/test_exos.py @@ -116,7 +116,7 @@ def test_clone(client: TestClient): print(rr.json()) assert rr.status_code == 200 assert 'id_code' in rr.json() - assert {**rr.json(), 'id_code': None} == {'name': 'test_exo', 'consigne': 'consigne', 'private': False, 'id_code': None, 'author': {'username': 'lilian2'}, 'original': {"id_code": id_code, "name": create['name']}, 'tags': [], 'exo_source': 'test.py', 'supports': { + assert {**rr.json(), 'id_code': None} == {'name': 'test_exo', 'consigne': 'consigne', 'private': False, 'id_code': None, 'author': {'username': 'lilian2'}, 'original': {"id_code": id_code, "name": create['name'], 'author': 'lilian'}, 'tags': [], 'exo_source': 'test.py', 'supports': { 'pdf': False, 'csv': True, 'web': True}, 'examples': {'type': 'csv', 'data': [{'calcul': '1 + ... = 2', "correction": "1 + [1] = 2"}, {'calcul': '1 + ... = 2', "correction": "1 + [1] = 2"}, {'calcul': '1 + ... = 2', "correction": "1 + [1] = 2"}]}, 'is_author': True} @@ -233,22 +233,25 @@ class Tags(BaseModel): def test_add_tags(client: TestClient, name='name', tags: List[Tags] = [{'label': "name", 'color': "#ff0000", - 'id_code': "tag_id"}], user=None): + 'id_code': None}], user=None, exo =None): if user == None: token = test_register(client, username="lilian")['access'] user = {"token": token, 'username': "lilian"} else: token = user['token'] - - exo = test_create(client, name=name, user=user) - id_code = exo['id_code'] + if exo == None: + exo = test_create(client, name=name, user=user) + id_code = exo['id_code'] + else: + id_code = exo['id_code'] r = client.post(f'/exercice/{id_code}/tags', json=tags, headers={'Authorization': 'Bearer ' + token}) - print(r.json()) + print("DATA", tags, "\n\n",r.json()) data = r.json() + labels = [l['label'] for l in tags] assert r.status_code == 200 - assert {**data, "tags": [{**t, "id_code": None} - for t in data['tags']]} == {**exo, 'tags': [*exo['tags'], *[{**t, 'id_code': None} for t in tags]]} + assert {'exo': {**data['exo'], 'tags': [{**t, "id_code": "test"} for t in data['exo']['tags'] if t['id_code'] != None]}, "tags": [{**t, "id_code": "test"} + for t in data['tags'] if t['id_code'] != None]} == {"exo": {**exo, 'tags': [{**t, "id_code": "test"} for t in tags]}, 'tags': [*exo['tags'], *[{**t, 'id_code': "test"} for t in tags if t['id_code'] == None]]} return r.json() @@ -281,19 +284,19 @@ def test_add_tags_too_long(client: TestClient): def test_remove_tag(client: TestClient): token = test_register(client, username="lilian")['access'] exo = test_add_tags(client, user={"token": token, 'username': "lilian"}) - id_code = exo['id_code'] + id_code = exo['exo']['id_code'] tag_id = exo["tags"][0]["id_code"] r = client.delete(f'/exercice/{id_code}/tags/{tag_id}', headers={'Authorization': 'Bearer ' + token}) print(r.json()) assert r.json() == { - **exo, 'tags': exo['tags'][1:]} + **exo['exo'], 'tags': exo['tags'][1:]} def test_remove_tag_not_found(client: TestClient): token = test_register(client, username="lilian")['access'] exo = test_add_tags(client, user={"token": token, 'username': "lilian"}) - id_code = exo['id_code'] + id_code = exo['exo']['id_code'] tag_id = "none" r = client.delete(f'/exercice/{id_code}/tags/{tag_id}', headers={'Authorization': 'Bearer ' + token}) @@ -316,7 +319,7 @@ def test_remove_tag_not_owner(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] exo = test_add_tags(client, user={"token": token, 'username': "lilian"}) - id_code = exo['id_code'] + id_code = exo['exo']['id_code'] tag_id = exo['tags'][0]['id_code'] r = client.delete(f'/exercice/{id_code}/tags/{tag_id}', headers={'Authorization': 'Bearer ' + token2}) @@ -481,29 +484,31 @@ def test_get_user_with_tags(client: TestClient): token2 = test_register(client, username="lilian2")['access'] tags1 = [{'label': "tag1", 'color': "#ff0000", 'id_code': None}] - tags2 = [{'label': "tag1", 'color': "#ff0000", 'id_code': None}, + tags2 = [ {'label': "tag2", 'color': "#ff0000", 'id_code': None}] - tags3 = [{'label': "tag1", 'color': "#ff0000", 'id_code': None}, - {'label': "tag2", 'color': "#ff0000", 'id_code': None}, {'label': "tag3", 'color': "#ff0000", 'id_code': None}] + + tags3 = [{'label': "tag3", 'color': "#ff0000", 'id_code': None}] exo_other_user = test_create( client, user={'token': token2, 'username': "lilian2"}) exo1 = test_add_tags(client, user={ 'token': token1, 'username': "lilian"}, tags=tags1) - + tags2 = [*exo1['tags'], *tags2] exo2 = test_add_tags(client, user={ 'token': token1, 'username': "lilian"}, tags=tags2) + + tags3 = [*exo2['tags'], *tags3] exo3 = test_add_tags(client, user={ 'token': token1, 'username': "lilian"}, tags=tags3) tags1 = exo1['tags'] tags2 = exo2['tags'] tags3 = exo3['tags'] - r = client.get('/exercices/user', params={'tags': [*[t['id_code'] for t in tags2], 'notexist']}, + r = client.get('/exercices/user', params={'tags': [*[t['id_code'] for t in tags2], 'notexisting']}, headers={'Authorization': 'Bearer ' + token1}) - print(r.json()) - assert r.json()['items'] == [exo2, exo3] + print("DATA", r.json()) + assert r.json()['items'] == [exo2['exo'], exo3['exo']] def test_get_user_with_tags_and_search(client: TestClient): @@ -511,20 +516,18 @@ def test_get_user_with_tags_and_search(client: TestClient): token2 = test_register(client, username="lilian2")['access'] tags1 = [{'label': "tag1", 'color': "#ff0000", 'id_code': None}] - tags2 = [{'label': "tag1", 'color': "#ff0000", 'id_code': None}, - {'label': "tag2", 'color': "#ff0000", 'id_code': None}] - tags3 = [{'label': "tag1", 'color': "#ff0000", 'id_code': None}, - {'label': "tag2", 'color': "#ff0000", 'id_code': None}, {'label': "tag3", 'color': "#ff0000", 'id_code': None}] + tags2 = [{'label': "tag2", 'color': "#ff0000", 'id_code': None}] + tags3 = [{'label': "tag3", 'color': "#ff0000", 'id_code': None}] exo_other_user = test_create( client, user={'token': token2, 'username': "lilian2"}) exo1 = test_add_tags(client, user={ 'token': token1, 'username': "lilian"}, tags=tags1, name="yes") - + tags2 = [*exo1['tags'], *tags2] exo2 = test_add_tags(client, user={ 'token': token1, 'username': "lilian"}, tags=tags2, name="no") - + tags3 = [*exo2['tags'], *tags3] exo3 = test_add_tags(client, user={ 'token': token1, 'username': "lilian"}, tags=tags3, name="yes") @@ -534,7 +537,7 @@ def test_get_user_with_tags_and_search(client: TestClient): r = client.get('/exercices/user', params={"search": "yes", 'tags': [t['id_code'] for t in tags2]}, headers={'Authorization': 'Bearer ' + token1}) print(r.json()) - assert r.json()['items'] == [exo3] + assert r.json()['items'] == [exo3['exo']] def test_get_public_auth(client: TestClient): @@ -595,8 +598,8 @@ def test_get_exo_no_auth(client: TestClient): token = test_register(client, username="lilian")['access'] exo = test_add_tags(client, user={'token': token, 'username': "lilian"}) - r = client.get('/exercice/' + exo['id_code']) - assert r.json() == {**exo, "tags": [], 'is_author': False} + r = client.get('/exercice/' + exo['exo']['id_code']) + assert r.json() == {**exo['exo'], "tags": [], 'is_author': False} def test_get_exo_no_auth_private(client: TestClient): @@ -613,20 +616,24 @@ def test_get_exo_auth(client: TestClient): token2 = test_register(client, username="lilian2")['access'] exo = test_add_tags(client, user={'token': token, 'username': "lilian"}) - r = client.get('/exercice/' + exo['id_code'], + r = client.get('/exercice/' + exo['exo']['id_code'], headers={'Authorization': 'Bearer ' + token2}) print(r.json(), exo) - assert r.json() == {**exo, "tags": [], 'is_author': False} + assert r.json() == {**exo['exo'], "tags": [], 'is_author': False} def test_get_exo_auth_with_tags(client: TestClient): token = test_register(client, username="lilian")['access'] + token2 = test_register(client, username="lilian2")['access'] + exo = test_add_tags(client, user={'token': token, 'username': "lilian"}) - - r = client.get('/exercice/' + exo['id_code'], + test_add_tags(client, user={'token': token2, 'username': "lilian2"}, exo={**exo['exo'], "is_author": False, "tags": []}) + + r = client.get('/exercice/' + exo['exo']['id_code'], headers={'Authorization': 'Bearer ' + token}) + print(r.json(), exo) - assert r.json() == {**exo} + assert r.json() == {**exo['exo']} def test_get_exo_auth_private(client: TestClient): @@ -668,7 +675,7 @@ def test_get_csv(client: TestClient): assert r.json()['items'] == [{**exoCsv.json(), 'is_author': False}] -def test_get_pdf(client: TestClient): +''' def test_get_pdf(client: TestClient): token = test_register(client)['access'] exoCsv = client.post('/exercices', data={"name": "name", "consigne": "consigne", "private": False}, files={ 'file': ('test.py', open('tests/testing_exo_source/exo_source_csv_only.py', 'rb'))}, headers={"Authorization": "Bearer " + token}) @@ -680,7 +687,7 @@ def test_get_pdf(client: TestClient): r = client.get('/exercices/public', params={"type": "pdf"}) assert r.json()['items'] == [{**exoPdf.json(), 'is_author': False}] - + ''' def test_get_web(client: TestClient): token = test_register(client)['access'] @@ -693,7 +700,7 @@ def test_get_web(client: TestClient): r = client.get('/exercices/public', params={"type": "web"}) - assert r.json() == [{**exoWeb.json(), 'is_author': False}] + assert r.json()['items'] == [{**exoWeb.json(), 'is_author': False}] def test_get_invalid_type(client: TestClient): diff --git a/backend/api/tests/test_room.py b/backend/api/tests/test_room.py index cc1e9dd..cce6589 100644 --- a/backend/api/tests/test_room.py +++ b/backend/api/tests/test_room.py @@ -1,13 +1,13 @@ -import random from fastapi import HTTPException from fastapi.testclient import TestClient + from tests.test_auth import test_register from tests.test_exos import test_create def test_create_room_no_auth(client: TestClient, public=False): r = client.post('/room', json={"name": "test_room", - "public": public}, params={'username': "lilian"}) + "public": public}, params={'username': "lilian"}) print(r.json()) assert "room" in r.json() assert "member" in r.json() @@ -16,8 +16,8 @@ def test_create_room_no_auth(client: TestClient, public=False): def test_create_room_no_auth_invalid(client: TestClient): - r = client.post('/room', json={"name": "test_room"*21, - "public": False}, params={'username': "lilian"*21}) + r = client.post('/room', json={"name": "test_room" * 21, + "public": False}, params={'username': "lilian" * 21}) print(r.json()) assert r.json() == {'detail': {'username_error': 'ensure this value has at most 20 characters', 'name_error': 'ensure this value has at most 20 characters'}} @@ -50,12 +50,13 @@ def test_login_no_auth(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as ws: ws.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) data = ws.receive_json() assert "id_code" in data['data']['member'] assert "clientId" in data['data']['member'] assert data == {'type': "loggedIn", "data": {"member": {**data['data']['member'], - "reconnect_code": member, "isAdmin": True, "isUser": False}}} + "reconnect_code": member, "isAdmin": True, + "isUser": False}}} def test_login_no_auth_different_clientId(client: TestClient): @@ -63,13 +64,13 @@ def test_login_no_auth_different_clientId(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as ws: ws.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) data = ws.receive_json() clientId = data['data']['member']['clientId'] ws.close() with client.websocket_connect(f"/ws/room/" + room['room']) as ws: ws.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) data = ws.receive_json() assert data['data']['member']['clientId'] != clientId @@ -78,11 +79,11 @@ def test_login_no_auth_not_in_room(client: TestClient): room = test_create_room_no_auth(client=client) with client.websocket_connect(f"/ws/room/" + room['room']) as ws: ws.send_json({"type": "login", "data": { - "reconnect_code": "lol"}}) + "reconnect_code": "lol"}}) data = ws.receive_json() print(data) - assert data == {'type': "error", "data": { - "msg": "Utilisateur introuvable dans cette salle"}} + assert data == {'type': "error", "data": {"code": 401, + "msg": "Utilisateur introuvable dans cette salle"}} def test_login_auth(client: TestClient): @@ -95,7 +96,8 @@ def test_login_auth(client: TestClient): print(data) assert "id_code" in data["data"]['member'] assert data == {'type': "loggedIn", "data": {"member": {**data["data"]['member'], - "username": "lilian", "isAdmin": True, "isUser": True, 'reconnect_code': "", "clientId": ""}}} + "username": "lilian", "isAdmin": True, "isUser": True, + 'reconnect_code': "", "clientId": ""}}} def test_login_auth_not_in_room(client: TestClient): @@ -106,8 +108,8 @@ def test_login_auth_not_in_room(client: TestClient): ws.send_json({"type": "login", "data": {"token": token}}) data = ws.receive_json() print(data) - assert data == {'type': "error", "data": { - "msg": "Utilisateur introuvable dans cette salle"}} + assert data == {'type': "error", "data": {"code": 401, + "msg": "Utilisateur introuvable dans cette salle"}} def test_join_auth(client: TestClient): @@ -116,7 +118,7 @@ def test_join_auth(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) admin.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as member: member.send_json({"type": "join", "data": {"token": token}}) @@ -132,14 +134,17 @@ def test_join_auth(client: TestClient): "waiter": {"waiter_id": mdata['data']['waiter']['waiter_id'], "username": "lilian2"}}} admin.send_json({"type": "accept", "data": { - "waiter_id": mdata['data']['waiter']['waiter_id']}}) + "waiter_id": mdata['data']['waiter']['waiter_id']}}) mdata = member.receive_json() - print('MDATA', mdata) assert mdata == {"type": "accepted", "data": {"member": { - "username": "lilian2", "isUser": True, "isAdmin": False, "reconnect_code": "", "id_code": mdata['data']["member"]["id_code"]}}} + "username": "lilian2", "isUser": True, "isAdmin": False, "reconnect_code": "", + "id_code": mdata['data']["member"]["id_code"], "clientId": "", "online": True}}} + print("MDATABUS", mdata) adata = admin.receive_json() + assert adata == {'type': "joined", 'data': { - "member": {"reconnect_code": "", "username": "lilian2", "isUser": True, "isAdmin": False, "id_code": adata['data']["member"]["id_code"]}}} + "member": {"reconnect_code": "", "clientId": "", "online": True, "username": "lilian2", "isUser": True, + "isAdmin": False, "id_code": adata['data']["member"]["id_code"]}}} admin.send_json({"type": "ping_room"}) mdata = member.receive_json() assert mdata == {"type": "ping", "data": {}} @@ -151,20 +156,22 @@ def test_join_waiter_not_found(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) admin.receive_json() admin.send_json({"type": "accept", "data": {"waiter_id": "OOOO"}}) data = admin.receive_json() - assert data == {"type": "error", "data": { - "msg": "Utilisateur en list d'attente introuvable"}} + assert data == {"type": "error", "data": {"code": 400, + "msg": "Utilisateur en liste d'attente introuvable"}} +# TODO = Maybe check the joined event + def test_join_no_auth(client: TestClient): room = test_create_room_no_auth(client=client) member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) admin.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as memberws: memberws.send_json( @@ -179,15 +186,19 @@ def test_join_no_auth(client: TestClient): "waiter": {"waiter_id": mdata['data']['waiter']['waiter_id'], "username": "member"}}} admin.send_json({"type": "accept", "data": { - "waiter_id": mdata['data']['waiter']['waiter_id']}}) + "waiter_id": mdata['data']['waiter']['waiter_id']}}) mdata = memberws.receive_json() new_reconnect = mdata['data']['member']['reconnect_code'] + clientId = mdata['data']['member']['clientId'] + assert clientId != "" assert 'reconnect_code' in mdata['data']['member'] assert mdata == {"type": "accepted", "data": {"member": { - "username": "member", "reconnect_code": new_reconnect, "isUser": False, "isAdmin": False, "id_code": mdata['data']["member"]["id_code"]}}} + "username": "member", "reconnect_code": new_reconnect, "isUser": False, "isAdmin": False, + "id_code": mdata['data']["member"]["id_code"], "clientId": clientId, "online": True}}} adata = admin.receive_json() assert adata == {'type': "joined", 'data': { - "member": {"reconnect_code": new_reconnect, "username": "member", "isUser": False, "isAdmin": False, "id_code": adata['data']["member"]["id_code"]}}} + "member": {"reconnect_code": new_reconnect, "clientId": "", "online": True, "username": "member", + "isUser": False, "isAdmin": False, "id_code": adata['data']["member"]["id_code"]}}} admin.send_json({"type": "ping_room"}) mdata = memberws.receive_json() assert mdata == {"type": "ping", "data": {}} @@ -202,8 +213,8 @@ def test_join_no_auth_username_error(client: TestClient): with client.websocket_connect(f"/ws/room/" + room['room']) as member: member.send_json({"type": "join", "data": {"username": "lilian"}}) mdata = member.receive_json() - assert mdata == {"type": "error", "data": { - "msg": "Nom d'utilisateur invalide ou indisponible"}} + assert mdata == {"type": "error", "data": {"code": 400, + "msg": "Nom d'utilisateur invalide ou indisponible"}} def test_join_no_auth_username_too_long(client: TestClient): @@ -211,10 +222,10 @@ def test_join_no_auth_username_too_long(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as member: - member.send_json({"type": "join", "data": {"username": "lilian"*21}}) + member.send_json({"type": "join", "data": {"username": "lilian" * 21}}) mdata = member.receive_json() - assert mdata == {"type": "error", "data": { - "msg": "Nom d'utilisateur invalide ou indisponible"}} + assert mdata == {"type": "error", "data": {"code": 400, + "msg": "Nom d'utilisateur invalide ou indisponible"}} def test_join_auth_refused(client: TestClient): @@ -223,7 +234,7 @@ def test_join_auth_refused(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) admin.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as member: member.send_json({"type": "join", "data": {"token": token}}) @@ -236,7 +247,7 @@ def test_join_auth_refused(client: TestClient): assert adata == {'type': "waiter", 'data': { "waiter": {"waiter_id": waiter_id, "username": "lilian2"}}} admin.send_json({"type": "refuse", "data": { - "waiter_id": waiter_id}}) + "waiter_id": waiter_id}}) adata = admin.receive_json() assert adata == {"type": "successfullyRefused", "data": {"waiter_id": waiter_id}} @@ -256,7 +267,8 @@ def test_join_auth_in_room_yet(client: TestClient): assert "id_code" in mdata['data']['member'] assert "clientId" in mdata['data']['member'] assert mdata == {"type": "loggedIn", "data": {"member": {**mdata['data']['member'], - "username": "lilian", "isAdmin": True, "isUser": True, 'reconnect_code': ""}}} + "username": "lilian", "isAdmin": True, "isUser": True, + 'reconnect_code': ""}}} def test_join_auth_public(client: TestClient): @@ -265,17 +277,20 @@ def test_join_auth_public(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) admin.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as member: member.send_json({"type": "join", "data": {"token": token}}) mdata = member.receive_json() assert mdata == {"type": "accepted", "data": {"member": { - "username": "lilian2", "isUser": True, "isAdmin": False, "reconnect_code": "", "id_code": mdata['data']["member"]["id_code"]}}} + "username": "lilian2", "isUser": True, "isAdmin": False, "reconnect_code": "", + "id_code": mdata['data']["member"]["id_code"], "clientId": "", "online": True}}} adata = admin.receive_json() assert adata == {'type': "joined", 'data': { - "member": {"reconnect_code": "", "username": "lilian2", "isUser": True, "isAdmin": False, "id_code": mdata['data']["member"]["id_code"]}}} + "member": {"reconnect_code": "", "username": "lilian2", "isUser": True, "isAdmin": False, + "id_code": mdata['data']["member"]["id_code"], + "clientId": "", "online": True}}} admin.send_json({"type": "ping_room"}) mdata = member.receive_json() assert mdata == {"type": "ping", "data": {}} @@ -286,19 +301,24 @@ def test_join_no_auth_public(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) admin.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as member: member.send_json({"type": "join", "data": {"username": "member"}}) mdata = member.receive_json() assert 'reconnect_code' in mdata['data']['member'] + assert "clientId" in mdata['data']['member'] and mdata['data']['member']['clientId'] != "" assert mdata == {"type": "accepted", "data": {"member": { - "username": "member", "reconnect_code": mdata['data']['member']['reconnect_code'], "isUser": False, "isAdmin": False, "id_code": mdata['data']["member"]["id_code"]}}} + "username": "member", "reconnect_code": mdata['data']['member']['reconnect_code'], "isUser": False, + "isAdmin": False, "id_code": mdata['data']["member"]["id_code"], + "clientId": mdata['data']['member']['clientId'], "online": True}}} adata = admin.receive_json() assert adata == {'type': "joined", 'data': { - "member": {"reconnect_code": mdata['data']['member']['reconnect_code'], "username": "member", "isUser": False, "isAdmin": False, "id_code": mdata['data']["member"]["id_code"]}}} + "member": {"reconnect_code": mdata['data']['member']['reconnect_code'], "username": "member", + "isUser": False, "isAdmin": False, "id_code": mdata['data']["member"]["id_code"], + "clientId": "", "online": True}}} member.send_json({"type": "update_groups"}) admin.send_json({"type": "ping_room"}) @@ -312,7 +332,7 @@ def test_join_auth_unauthorized(client: TestClient): member = room['member'] with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { - "reconnect_code": member}}) + "reconnect_code": member}}) admin.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as member: member.send_json({"type": "join", "data": {"token": token}}) @@ -338,15 +358,16 @@ def test_connect_admin(client: TestClient): members = room['members'] with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { - "reconnect_code": members[0]}}) + "reconnect_code": members[0]}}) admin.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as member: member.send_json({'type': "login", "data": { - "reconnect_code": members[1]}}) + "reconnect_code": members[1]}}) adata = admin.receive_json() assert adata == {"type": "connect", "data": {"member": { - "username": "member", "reconnect_code": members[1], "isAdmin": False, "isUser": False, "id_code": adata['data']["member"]["id_code"]}}} + "username": "member", "reconnect_code": members[1], "isAdmin": False, "isUser": False, + "id_code": adata['data']["member"]["id_code"], "clientId": "", "online": True}}} def test_connect_member(client: TestClient): @@ -358,11 +379,12 @@ def test_connect_member(client: TestClient): memberws.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({'type': "login", "data": { - "reconnect_code": members[0]}}) + "reconnect_code": members[0]}}) mdata = memberws.receive_json() assert mdata == {"type": "connect", "data": {"member": { - "username": "lilian", "reconnect_code": "", "isAdmin": True, "isUser": False, "id_code": mdata['data']["member"]["id_code"]}}} + "username": "lilian", "reconnect_code": "", "isAdmin": True, "isUser": False, + "id_code": mdata['data']["member"]["id_code"], "clientId": "", "online": True}}} def test_disconnect(client: TestClient): @@ -374,13 +396,33 @@ def test_disconnect(client: TestClient): memberws.receive_json() with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({'type': "login", "data": { - "reconnect_code": members[0]}}) + "reconnect_code": members[0]}}) memberws.receive_json() admin.close() mdata = memberws.receive_json() assert mdata == {"type": "disconnect", "data": {"member": { - "username": "lilian", "reconnect_code": "", "isAdmin": True, "isUser": False, "id_code": mdata['data']["member"]["id_code"]}}} + "username": "lilian", "reconnect_code": "", "isAdmin": True, "isUser": False, + "id_code": mdata['data']["member"]["id_code"], "clientId": "", "online": False}}} + + +def test_disconnect_admin(client: TestClient): + room = test_join_no_auth(client=client) + members = room['members'] + with client.websocket_connect(f"/ws/room/" + room['room']) as admin: + admin.send_json({"type": "login", "data": { + "reconnect_code": members[0]}}) + admin.receive_json() + with client.websocket_connect(f"/ws/room/" + room['room']) as memberws: + memberws.send_json({'type': "login", "data": { + "reconnect_code": members[1]}}) + memberws.receive_json() + memberws.close() + admin.receive_json() + adata = admin.receive_json() + assert adata == {"type": "disconnect", "data": {"member": { + "username": "member", "reconnect_code": members[1], "isAdmin": False, "isUser": False, + "id_code": adata['data']["member"]["id_code"], "clientId": "", "online": False}}} def test_disconnect_waiter(client: TestClient): @@ -421,7 +463,8 @@ def test_leave(client: TestClient): adata = admin.receive_json() assert adata == {"type": "leaved", "data": {"member": { - "username": "test", "reconnect_code": m["data"]['member']["reconnect_code"], "isAdmin": False, "isUser": False, "id_code": adata['data']["member"]["id_code"]}}} + "username": "test", "reconnect_code": "", "isAdmin": False, + "isUser": False, "id_code": adata['data']["member"]["id_code"], "online": True, "clientId": ""}}} def test_leave_not_connected(client: TestClient): @@ -431,8 +474,8 @@ def test_leave_not_connected(client: TestClient): with client.websocket_connect(f"/ws/room/" + room['room']) as memberws: memberws.send_json({"type": "leave"}) data = memberws.receive_json() - assert data == {"type": "error", "data": { - "msg": "Vous n'êtes connecté à aucune salle"}} + assert data == {"type": "error", "data": {"code": 400, + "msg": "Vous n'êtes connecté à aucune salle"}} def test_leave_admin(client: TestClient): @@ -445,10 +488,11 @@ def test_leave_admin(client: TestClient): admin.receive_json() admin.send_json({"type": "leave"}) data = admin.receive_json() - assert data == {"type": "error", "data": { - "msg": "Vous ne pouvez pas quitter une salle dont vous êtes l'administrateur"}} + assert data == {"type": "error", "data": {"code": 400, + "msg": "Vous ne pouvez pas quitter une salle dont vous êtes l'administrateur"}} +# TODO : Change type for ban and leave def test_ban_anonymous(client: TestClient): room = test_create_room_no_auth(client=client, public=True) member = room['member'] @@ -470,7 +514,8 @@ def test_ban_anonymous(client: TestClient): adata = admin.receive_json() assert adata == {"type": "leaved", "data": {"member": { - "username": "test", "reconnect_code": reconnect_code, "isUser": False, "isAdmin": False, "id_code": id_code}}} + "username": "test", "reconnect_code": "", "isUser": False, "isAdmin": False, + "id_code": id_code, "clientId": "", "online": True}}} def test_ban_anonymous_unauthorized(client: TestClient): @@ -500,8 +545,8 @@ def test_ban_admin(client: TestClient): admin.send_json( {"type": "ban", "data": {"member_id": data['data']['member']['id_code']}}) a = admin.receive_json() - assert a == {'type': "error", "data": { - "msg": "Vous ne pouvez pas bannir un administrateur"}} + assert a == {'type': "error", "data": {"code": 400, + "msg": "Vous ne pouvez pas bannir un administrateur"}} def test_ban_user(client: TestClient): @@ -518,13 +563,14 @@ def test_ban_user(client: TestClient): admin.receive_json() admin.send_json( - {"type": "ban", "data": {"member_id": m['data']['member']['id_code']}}) + {"type": "ban", "data": {"member_id": m['data']['member']['id_code']}}) mdata = memberws.receive_json() assert mdata == {"type": "banned", "data": {}} adata = admin.receive_json() assert adata == {"type": "leaved", "data": {"member": { - "username": "lilian2", "reconnect_code": "", "isUser": True, "isAdmin": False, "id_code": m['data']['member']['id_code']}}} + "username": "lilian2", "reconnect_code": "", "isUser": True, "isAdmin": False, + "id_code": m['data']['member']['id_code'], "clientId": "", "online": True}}} def test_create_parcours_no_auth(client: TestClient): @@ -533,26 +579,44 @@ def test_create_parcours_no_auth(client: TestClient): token = test_register(client, username="lilian") exo1 = test_create(name="test1", client=client, user={ - "token": token['access'], "username": "lilian"}) + "token": token['access'], "username": "lilian"}) exo2 = test_create(name="test2", client=client, user={ - "token": token['access'], "username": "lilian"}) + "token": token['access'], "username": "lilian"}) exo3 = test_create(name="test3", client=client, user={ - "token": token['access'], "username": "lilian"}) + "token": token['access'], "username": "lilian"}) exo4 = test_create(name="test4", client=client, user={ - "token": token['access'], "username": "lilian"}) + "token": token['access'], "username": "lilian"}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { "reconnect_code": member}}) adata = admin.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', json={"clientId": adata['data']['member']['clientId'], "parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": "lolilol", "quantity": 12}, {"exercice_id": exo1['id_code'], "quantity": 10}, {"exercice_id": exo2['id_code'], "quantity": 5}, {"exercice_id": exo3['id_code'], "quantity": 12}]}}) - print('resonse', r.json()) - assert r.json() == {"id_code": r.json()['id_code'], "challenges": [], "name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [{ - "exercice_id": exo1['id_code'], "name": exo1['name'], "quantity": 10}, {"exercice_id": exo2['id_code'], "name": exo2['name'], "quantity": 5}, {"exercice_id": exo3['id_code'], "name": exo3['name'], "quantity": 12}]} + r = client.post(f'/room/{room["room"]}/parcours', params={"clientId": adata['data']['member']['clientId']}, + json={ + "name": "test_parcours", "time": 10 * 60, + "max_mistakes": 10, "exercices": [ + {"exercice_id": "lolilol", "quantity": 12}, + {"exercice_id": exo1['id_code'], + "quantity": 10}, + {"exercice_id": exo2['id_code'], "quantity": 5}, + {"exercice_id": exo3['id_code'], + "quantity": 12}]}) + print('resonse', r.json(), exo3) + assert r.json() == {"id_code": r.json()['id_code'], "memberRank": None, "name": "test_parcours", "pb": None, + "rank": None, "ranking": [], "tops": [], "validated": False, "challenges": {}, + "name": "test_parcours", "time": 10 * 60, + "max_mistakes": 10, "exercices": [{"examples": exo1['examples'], + "exercice_id": exo1['id_code'], "name": exo1['name'], + "quantity": 10}, + {"examples": exo2['examples'], + "exercice_id": exo2['id_code'], "name": exo2['name'], + "quantity": 5}, + {"examples": exo3['examples'], + "exercice_id": exo3['id_code'], "name": exo3['name'], + "quantity": 12}]} adata = admin.receive_json() assert adata == {"type": "add_parcours", "data": {"parcours": { - "name": "test_parcours", "id_code": r.json()['id_code'], "best_note": None}}} + "name": "test_parcours", "id_code": r.json()['id_code'], "best_note": None, "validated": False, }}} return r.json(), member, room @@ -560,25 +624,42 @@ def test_create_parcours_auth(client: TestClient): token = test_register(client, username="lilian")['access'] room = test_create_room_auth(client=client, token=token) exo1 = test_create(name="test1", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) exo2 = test_create(name="test2", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) exo3 = test_create(name="test3", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) exo4 = test_create(name="test4", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { "token": token}}) adata = admin.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": "lolilol", "quantity": 12}, {"exercice_id": exo1['id_code'], "quantity": 10}, {"exercice_id": exo2['id_code'], "quantity": 5}, {"exercice_id": exo3['id_code'], "quantity": 12}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={ + "name": "test_parcours", "time": 10 * 60, + "max_mistakes": 10, "exercices": [ + {"exercice_id": "lolilol", "quantity": 12}, + {"exercice_id": exo1['id_code'], + "quantity": 10}, + {"exercice_id": exo2['id_code'], "quantity": 5}, + {"exercice_id": exo3['id_code'], + "quantity": 12}]}) - assert r.json() == {"id_code": r.json()['id_code'], "challenges": [], "name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [{ - "exercice_id": exo1['id_code'], "name": exo1['name'], "quantity": 10}, {"exercice_id": exo2['id_code'], "name": exo2['name'], "quantity": 5}, {"exercice_id": exo3['id_code'], "name": exo3['name'], "quantity": 12}]} + assert r.json() == {"id_code": r.json()['id_code'], "memberRank": None, "name": "test_parcours", "pb": None, + "rank": None, "ranking": [], "tops": [], "validated": False, "challenges": {}, + "name": "test_parcours", "time": 10 * 60, + "max_mistakes": 10, "exercices": [{"examples": exo1['examples'], + "exercice_id": exo1['id_code'], "name": exo1['name'], + "quantity": 10}, + {"examples": exo2['examples'], + "exercice_id": exo2['id_code'], "name": exo2['name'], + "quantity": 5}, + {"examples": exo3['examples'], + "exercice_id": exo3['id_code'], "name": exo3['name'], + "quantity": 12}]} adata = admin.receive_json() assert adata == {"type": "add_parcours", "data": {"parcours": { - "name": "test_parcours", "id_code": r.json()['id_code'], "best_note": None}}} + "name": "test_parcours", "id_code": r.json()['id_code'], "best_note": None, "validated": False, }}} return r.json(), token, room @@ -596,8 +677,9 @@ def test_create_parcours_unauthorized(client: TestClient): mdata = memberws.send_json( {"type": "join", "data": {"token": token}}) - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, - "exercices": [{"exercice_id": "lolilol", "quantity": 12}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, + json={"parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, + "exercices": [{"exercice_id": "lolilol", "quantity": 12}]}}) assert r.status_code == 401 assert r.json() == { "detail": "Vous devez être administrateur pour faire cela"} @@ -613,8 +695,9 @@ def test_create_parcours_not_in_room(client: TestClient): "reconnect_code": member}}) adata = admin.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, - "exercices": [{"exercice_id": "lolilol", "quantity": 12}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, + json={"parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, + "exercices": [{"exercice_id": "lolilol", "quantity": 12}]}}) assert r.status_code == 401 assert r.json() == { "detail": "Vous n'êtes pas dans cette salle"} @@ -625,15 +708,23 @@ def test_create_parcours_no_valid_exo(client: TestClient): member = room['member'] token = test_register(client, username="lilian")['access'] exoCsv = client.post('/exercices', data={"name": "name", "consigne": "consigne", "private": False}, files={ - 'file': ('test.py', open('tests/testing_exo_source/exo_source_csv_only.py', 'rb'))}, headers={"Authorization": "Bearer " + token}) + 'file': ('test.py', open('tests/testing_exo_source/exo_source_csv_only.py', 'rb'))}, + headers={"Authorization": "Bearer " + token}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { "reconnect_code": member}}) adata = admin.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', json={"clientId": adata['data']['member']['clientId'], "parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, - "exercices": [{"exercice_id": "lolilol", "quantity": 12}, {"exercice_id": exoCsv.json()['id_code'], "quantity": 10}]}}) + r = client.post(f'/room/{room["room"]}/parcours', params={"clientId": adata['data']['member']['clientId']}, + json={"name": "test_parcours", "time": 10 * 60, + "max_mistakes": 10, + "exercices": [{"exercice_id": "lolilol", + "quantity": 12}, { + "exercice_id": + exoCsv.json()[ + 'id_code'], + "quantity": 10}]}) assert r.status_code == 400 assert r.json() == { "detail": "Veuillez entrer au moins un exercice valide"} @@ -648,16 +739,18 @@ def test_create_parcours_not_authenticated(client: TestClient): "reconnect_code": member}}) admin.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, - "exercices": [{"exercice_id": "lolilol", "quantity": 12}]}}) + r = client.post(f'/room/{room["room"]}/parcours', + json={"parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, + "exercices": [{"exercice_id": "lolilol", "quantity": 12}]}}) assert r.status_code == 401 assert r.json() == { "detail": "Not authenticated"} def test_create_parcours_room_not_found(client: TestClient): - r = client.post(f'/room/test/parcours', json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, - "exercices": [{"exercice_id": "lolilol", "quantity": 12}]}}) + r = client.post(f'/room/test/parcours', + json={"parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, + "exercices": [{"exercice_id": "lolilol", "quantity": 12}]}}) assert r.status_code == 404 assert r.json() == {"detail": "Salle introuvable"} @@ -669,7 +762,7 @@ def test_delete_parcours_auth(client: TestClient): "token": token}}) adata = admin.receive_json() r = client.delete(f"/room/{room['room']}/parcours/{parcours['id_code']}", headers={ - "Authorization": "Bearer " + token}) + "Authorization": "Bearer " + token}) assert r.json() == "ok" adata = admin.receive_json() assert adata == {'type': "del_parcours", @@ -682,8 +775,8 @@ def test_delete_parcours_no_auth(client: TestClient): admin.send_json({"type": "login", "data": { "reconnect_code": reconnect}}) adata = admin.receive_json() - r = client.delete(f"/room/{room['room']}/parcours/{parcours['id_code']}", json={ - "clientId": adata['data']['member']['clientId']}) + r = client.delete(f"/room/{room['room']}/parcours/{parcours['id_code']}", params={ + "clientId": adata['data']['member']['clientId']}) assert r.json() == "ok" adata = admin.receive_json() assert adata == {'type': "del_parcours", @@ -701,7 +794,7 @@ def test_delete_parcours_unauthorized(client: TestClient): {"type": "join", "data": {"token": token}}) adata = admin.receive_json() r = client.delete(f"/room/{room['room']}/parcours/{parcours['id_code']}", headers={ - "Authorization": "Bearer " + token}) + "Authorization": "Bearer " + token}) assert r.status_code == 401 assert r.json() == { "detail": "Vous devez être administrateur pour faire cela"} @@ -724,12 +817,22 @@ def validate_entry(out: str): return out[:-1] == "1 + [0] = " and int(out[-1]) <= 9 and int(out[-1]) >= 1 +def fill_challenge(c): + for ex in c: + ex['data'] = [{**e, "inputs": [{**i, "value": str(id)} for i in e['inputs']]} for id,e in enumerate(ex['data'])] + return c + +def corrige_challenge(c, corr): + for ex in c: + ex['data'] = [{**e, "inputs": [{**i, "correction": corr, "valid": i['value'] == corr} for i in e['inputs']]} for id,e in enumerate(ex['data'])] + return c + def test_challenge_auth(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) exo1 = test_create(name="test1", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { @@ -740,35 +843,65 @@ def test_challenge_auth(client: TestClient): memberws.send_json( {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() + id_code = mdata['data']['member']['id_code'] - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": exo1['id_code'], "quantity": 3}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, + json={"name": "test_parcours", "time": 10 * 60, "max_mistakes": 10, "exercices": [ + {"exercice_id": exo1['id_code'], "quantity": 3}]}) - parcours_id = r.json()['id_code'] - memberws.receive_json() - admin.receive_json() # new parcours - admin.receive_json() # disconnect - admin.receive_json() - admin.send_json({"type": "sub_parcours", "data": { - "parcours_id": parcours_id}}) - r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', - headers={"Authorization": "Bearer " + token2}) - data = r.json()['challenge'] + parcours_id = r.json()['id_code'] - data = r.json()['challenge'] - assert "id_code" in r.json() - assert len(data) == 1 and len(data[0]) == 3 and all([e['inputs'] == [ - {'index': 0, "value": ""}] and validate_entry(e['calcul']) for e in data[0]]) - filled_obj = [ - [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] - r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) - assert r.json() == {"id_code": r.json()['id_code'], "note": {"value": 1, "total": 3}, "time": 162, "validated": False, "isCorriged": True, "data": [ - [{**e, 'inputs': [{**e['inputs'][0], "correction": '1'}]} for e in filled_obj[0]]]} + # memberws.receive_json() + admin.receive_json() # new parcours + memberws.receive_json() + admin.receive_json() - adata = admin.receive_json() - assert adata == {'type': 'challenge', 'data': {'challenger': mdata['data']['member']['id_code'], 'note': { - 'value': 1, 'total': 3}, 'time': 162,"validated": False, 'isCorriged': True, 'canCorrige': True, "id_code": r.json()['id_code']}} + admin.send_json({"type": "sub_parcours", "data": { + "parcours_id": parcours_id}}) + memberws.send_json({"type": "sub_parcours", "data": { + "parcours_id": parcours_id}}) + + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', + headers={"Authorization": "Bearer " + token2}) + + data = r.json()['challenge'] + assert "id_code" in r.json() and r.json()['id_code'] != "" + assert len(data) == 1 and len(data[0]['data']) == 3 and all([e['inputs'] == [ + {'index': 0, "value": ""}] and validate_entry(e['calcul']) for e in data[0]['data']]) + + filled_obj = [ + [{**e, 'inputs': [{"index": 0, "value": str(i)}]} for i, e in enumerate(data[0]['data'])]] + + filled_obj = fill_challenge(data) + + r = client.post( + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, + headers={"Authorization": "Bearer " + token2}) + + assert r.json()['id_code'] != "" + assert r.json() == {"id_code": r.json()['id_code'], "mistakes": 2, "time": 162, + "validated": True, "isCorriged": True, "data": corrige_challenge(filled_obj, "1")} + + adata = admin.receive_json() + assert adata == {'type': 'challenge', 'data': { + 'challenger': {"id_code": mdata['data']['member']['id_code'], + "name": mdata['data']['member']['username']}, + 'challenges': [ + {"canCorrige": True, "id_code": r.json()['id_code'], "mistakes": 2, "time": 162, "isCorriged": True, + "validated": True}]}} + + admin.receive_json() + adata = admin.receive_json() + + memberws.receive_json() + mdata = memberws.receive_json() + assert mdata == {'type': "newRanks", 'data': {"rank": 1, "avgRank": 1}} + mdata = memberws.receive_json() + + assert mdata == adata == {'type': 'newTops', 'data': { + 'tops': [{'challenger': {'name': 'lilian2', 'id_code': id_code}, 'mistakes': 2, 'time': 162}], + 'avgTops': [{'id_code': id_code, 'avg': 2.0, 'name': 'lilian2'}]}} def test_corrige_auth(client: TestClient): @@ -776,7 +909,7 @@ def test_corrige_auth(client: TestClient): token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) exo1 = test_create(name="test1", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { @@ -787,59 +920,81 @@ def test_corrige_auth(client: TestClient): memberws.send_json( {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() + id_code = mdata['data']['member']['id_code'] - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": exo1['id_code'], "quantity": 3}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, + json={"name": "test_parcours", "time": 10 * 60, "max_mistakes": 10, "exercices": [ + {"exercice_id": exo1['id_code'], "quantity": 3}]}) + print(r.json()) + parcours_id = r.json()['id_code'] + memberws.receive_json() + admin.receive_json() # new parcours + # admin.receive_json() # disconnect + admin.receive_json() + memberws.send_json({"type": "sub_parcours", "data": { + "parcours_id": parcours_id}}) + admin.send_json({"type": "sub_parcours", "data": { + "parcours_id": parcours_id}}) + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', + headers={"Authorization": "Bearer " + token2}) - parcours_id = r.json()['id_code'] - memberws.receive_json() - admin.receive_json() # new parcours - admin.receive_json() # disconnect - admin.receive_json() - admin.send_json({"type": "sub_parcours", "data": { - "parcours_id": parcours_id}}) - r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', - headers={"Authorization": "Bearer " + token2}) - data = r.json()['challenge'] + data = r.json()['challenge'] + assert "id_code" in r.json() + assert len(data) == 1 and len(data[0]['data']) == 3 and all([e['inputs'] == [ + {'index': 0, "value": ""}] and validate_entry(e['calcul']) for e in data[0]['data']]) + filled_obj = fill_challenge(data) + r = client.post( + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, + headers={"Authorization": "Bearer " + token2}) - data = r.json()['challenge'] - assert "id_code" in r.json() - assert len(data) == 1 and len(data[0]) == 3 and all([e['inputs'] == [ - {'index': 0, "value": ""}] and validate_entry(e['calcul']) for e in data[0]]) - filled_obj = [ - [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] - r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) - - assert r.json() == {"id_code": r.json()['id_code'], "note": {"value": 1, "total": 3}, "time": 162, "validated": False, "isCorriged": True, "data": [ - [{**e, 'inputs': [{**e['inputs'][0], "correction": '1'}]} for e in filled_obj[0]]]} + assert r.json()['id_code'] != "" + assert r.json() == {"id_code": r.json()['id_code'], "mistakes": 2, "time": 162, + "validated": True, "isCorriged": True, "data": corrige_challenge(filled_obj, "1")} + adata = admin.receive_json() + assert adata == {'type': 'challenge', 'data': { + 'challenger': {"id_code": mdata['data']['member']['id_code'], + "name": mdata['data']['member']['username']}, + 'challenges': [ + {"canCorrige": True, "id_code": r.json()['id_code'], "mistakes": 2, "time": 162, "isCorriged": True, + "validated": True}]}} - adata = admin.receive_json() - assert adata == {'type': 'challenge', 'data': {'challenger': mdata['data']['member']['id_code'], 'note': { - 'value': 1, 'total': 3}, 'time': 162, 'isCorriged': True,"validated": False, 'canCorrige': True, "id_code": r.json()['id_code']}} - - - rr = client.get( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', headers={"Authorization": "Bearer " + token2}) - - assert rr.json() == r.json() + rr = client.get( + f'/room/{room["room"]}/correction/{r.json()["id_code"]}', + headers={"Authorization": "Bearer " + token2}) + assert rr.json() == r.json() - data = r.json()['data'] - corriged_obj = [ - [{**e, 'inputs': [{**e['inputs'][0], "correction": str(data[0].index(e))}]} for e in data[0]]] - r = client.put( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'correction': corriged_obj}, headers={"Authorization": "Bearer " + token}) - assert r.json() == {"id_code": r.json()['id_code'], "note": {"value": 3, "total": 3}, "time": 162, "validated": True, "isCorriged": True, "data": corriged_obj} - - adata = admin.receive_json() - assert adata == {'type': 'challenge_change', 'data': {'challenger': mdata['data']['member']['id_code'], 'note': { - 'value': 3, 'total': 3}, 'time': 162,"validated": True, 'isCorriged': True, 'canCorrige': True, "id_code": r.json()['id_code']}} - rr = client.get( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', headers={"Authorization": "Bearer " + token2}) + data = r.json()['data'] + corriged_obj = [ + [{**e, 'inputs': [{**e['inputs'][0], "correction": str(i)}]} for i, e in enumerate(data[0]['data'])]] + corriged_obj = corrige_challenge(filled_obj, "0") + r = client.put( + f'/room/{room["room"]}/challenge/{r.json()["id_code"]}', + json={'correction': corriged_obj}, + headers={"Authorization": "Bearer " + token}) - assert rr.json() == r.json() + assert r.json() == {"id_code": r.json()['id_code'], "mistakes": 3, "time": 162, + "validated": False, "isCorriged": True, "data": corriged_obj} + + adata = admin.receive_json() + assert adata == {'type': 'challenge', 'data': { + 'challenger': {"id_code": mdata['data']['member']['id_code'], + "name": mdata['data']['member']['username']}, + 'challenges': [ + {"canCorrige": True, "id_code": r.json()['id_code'], "mistakes": 2, "time": 162, "isCorriged": True, + "validated": True}]}} + + assert adata == {'type': 'challenge_change', + 'data': {'challenger': mdata['data']['member']['id_code'], 'note': { + 'value': 3, 'total': 3}, 'time': 162, "validated": True, 'isCorriged': True, + 'canCorrige': True, "id_code": r.json()['id_code']}} + rr = client.get( + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + headers={"Authorization": "Bearer " + token2}) + + assert rr.json() == r.json() def test_get_challenge_auth(client: TestClient): @@ -847,7 +1002,7 @@ def test_get_challenge_auth(client: TestClient): token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) exo1 = test_create(name="test1", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { @@ -859,8 +1014,9 @@ def test_get_challenge_auth(client: TestClient): {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": exo1['id_code'], "quantity": 3}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={ + "parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, "exercices": [ + {"exercice_id": exo1['id_code'], "quantity": 3}]}}) parcours_id = r.json()['id_code'] memberws.receive_json() @@ -868,7 +1024,7 @@ def test_get_challenge_auth(client: TestClient): admin.receive_json() # disconnect admin.receive_json() admin.send_json({"type": "sub_parcours", "data": { - "parcours_id": parcours_id}}) + "parcours_id": parcours_id}}) r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token2}) @@ -879,22 +1035,26 @@ def test_get_challenge_auth(client: TestClient): filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) + + assert r.json() == {"id_code": r.json()['id_code'], "note": {"value": 1, "total": 3}, "time": 162, + "validated": False, "isCorriged": True, "data": [ + [{**e, 'inputs': [{**e['inputs'][0], "correction": '1'}]} for e in filled_obj[0]]]} - assert r.json() == {"id_code": r.json()['id_code'], "note": {"value": 1, "total": 3}, "time": 162, "validated": False, "isCorriged": True, "data": [ - [{**e, 'inputs': [{**e['inputs'][0], "correction": '1'}]} for e in filled_obj[0]]]} - rr = client.get( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', headers={"Authorization": "Bearer " + token2}) + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + headers={"Authorization": "Bearer " + token2}) assert rr.json() == r.json() + def test_challenge_auth_bad_obj(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) exo1 = test_create(name="test1", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { @@ -906,50 +1066,63 @@ def test_challenge_auth_bad_obj(client: TestClient): {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": exo1['id_code'], "quantity": 3}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={ + "parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, "exercices": [ + {"exercice_id": exo1['id_code'], "quantity": 3}]}}) parcours_id = r.json()['id_code'] memberws.receive_json() - - + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token2}) - + data = r.json()['challenge'] - id_code = r.json()['id_code'] + id_code = r.json()['id_code'] filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', json={'challenge': [*filled_obj, "test"], "time": 162}, headers={"Authorization": "Bearer " + token2}) + f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', + json={'challenge': [*filled_obj, "test"], "time": 162}, headers={"Authorization": "Bearer " + token2}) assert r.json() == {'detail': { 'challenge_error': 'value is not a valid list'}} - + r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', json={'challenge': [*filled_obj, ["test"]], "time": 162}, headers={"Authorization": "Bearer " + token2}) + f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', + json={'challenge': [*filled_obj, ["test"]], "time": 162}, headers={"Authorization": "Bearer " + token2}) assert r.json() == {'detail': { 'challenge_error': 'value is not a valid dict'}} r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', json={'challenge': [*filled_obj, [{"test": "test"}]], "time": 162}, headers={"Authorization": "Bearer " + token2}) + f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', + json={'challenge': [*filled_obj, [{"test": "test"}]], "time": 162}, + headers={"Authorization": "Bearer " + token2}) assert r.json() == {'detail': { 'calcul_error': 'field required', 'inputs_error': 'field required'}} r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', json={'challenge': [*filled_obj, filled_obj[0]], "time": 162}, headers={"Authorization": "Bearer " + token2}) - assert r.json() == {'detail': {"challenge_error": "Object does not correspond to correction"}} - + f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', + json={'challenge': [*filled_obj, filled_obj[0]], "time": 162}, + headers={"Authorization": "Bearer " + token2}) + assert r.json() == {'detail': { + "challenge_error": "Object does not correspond to correction"}} + r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', json={'challenge': [*filled_obj, filled_obj[0]], "time": 162}, headers={"Authorization": "Bearer " + token2}) - assert r.json() == {'detail': {"challenge_error": "Object does not correspond to correction"}} + f'/room/{room["room"]}/challenge/{parcours_id}/{id_code}', + json={'challenge': [*filled_obj, filled_obj[0]], "time": 162}, + headers={"Authorization": "Bearer " + token2}) + assert r.json() == {'detail': { + "challenge_error": "Object does not correspond to correction"}} + def stripKey(dict, key): - return {k:v for k,v in dict.items() if k != key} + return {k: v for k, v in dict.items() if k != key} + + def test_get_parcours_member(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) exo1 = test_create(name="test1", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { @@ -961,53 +1134,61 @@ def test_get_parcours_member(client: TestClient): {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": exo1['id_code'], "quantity": 3}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={ + "parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, "exercices": [ + {"exercice_id": exo1['id_code'], "quantity": 3}]}}) parcours_id = r.json()['id_code'] memberws.receive_json() - - + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token2}) data = r.json()['challenge'] filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) - challenge1 = {**stripKey(r.json(), "data"), "challenger": mdata['data']['member']['id_code'], "canCorrige": True} - + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) + challenge1 = {**stripKey(r.json(), "data"), + "challenger": mdata['data']['member']['id_code'], "canCorrige": True} + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token2}) data = r.json()['challenge'] filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) - challenge2 ={**stripKey(r.json(), "data"), "challenger": mdata['data']['member']['id_code'], "canCorrige": True} - - + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) + challenge2 = {**stripKey(r.json(), "data"), + "challenger": mdata['data']['member']['id_code'], "canCorrige": True} + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token}) data = r.json()['challenge'] filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token}) - challenge3 ={**stripKey(r.json(), "data"), "challenger": mdata['data']['member']['id_code'], "canCorrige": True} - - + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token}) + challenge3 = {**stripKey(r.json(), "data"), + "challenger": mdata['data']['member']['id_code'], "canCorrige": True} + r = client.get(url=f'/room/{room["room"]}/parcours/{parcours_id}', headers={"Authorization": "Bearer " + token2}) - assert r.json() == {'name': 'test_parcours', 'time': 600, 'validate_condition': 10, 'id_code': parcours_id, 'exercices': [{'exercice_id': exo1['id_code'], 'quantity': 3, 'name': 'test1'}], 'challenges': [challenge1, challenge2]} - + assert r.json() == {'name': 'test_parcours', 'time': 600, 'validate_condition': 10, 'id_code': parcours_id, + 'exercices': [ + {'exercice_id': exo1['id_code'], 'quantity': 3, 'name': 'test1'}], + 'challenges': [challenge1, challenge2]} + + def test_get_parcours_admin(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) exo1 = test_create(name="test1", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { @@ -1019,60 +1200,67 @@ def test_get_parcours_admin(client: TestClient): {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": exo1['id_code'], "quantity": 3}]}}) + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={ + "parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, "exercices": [ + {"exercice_id": exo1['id_code'], "quantity": 3}]}}) parcours_id = r.json()['id_code'] memberws.receive_json() - - + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token2}) data = r.json()['challenge'] filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) - challenge1 = {**stripKey(r.json(), "data"), "challenger": mdata['data']['member']['id_code'], "canCorrige": True} - + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) + challenge1 = {**stripKey(r.json(), "data"), + "challenger": mdata['data']['member']['id_code'], "canCorrige": True} + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token2}) data = r.json()['challenge'] filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) - challenge2 ={**stripKey(r.json(), "data"), "challenger": mdata['data']['member']['id_code'], "canCorrige": True} - - + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) + challenge2 = {**stripKey(r.json(), "data"), + "challenger": mdata['data']['member']['id_code'], "canCorrige": True} + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token}) data = r.json()['challenge'] filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token}) - challenge3 ={**stripKey(r.json(), "data"), "challenger": adata['data']['member']['id_code'], "canCorrige": True} - - + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token}) + challenge3 = {**stripKey(r.json(), "data"), + "challenger": adata['data']['member']['id_code'], "canCorrige": True} + r = client.get(url=f'/room/{room["room"]}/parcours/{parcours_id}', headers={"Authorization": "Bearer " + token}) - assert {**r.json(), "challenges": r.json()['challenges'].sort(key=lambda d: d['id_code']) }== {'name': 'test_parcours', 'time': 600, 'validate_condition': 10, 'id_code': parcours_id, 'exercices': [{'exercice_id': exo1['id_code'], 'quantity': 3, 'name': 'test1'}], 'challenges': [challenge1, challenge2, challenge3].sort(key = lambda d: d['id_code'])} - - + assert {**r.json(), "challenges": r.json()['challenges'].sort(key=lambda d: d['id_code'])} == { + 'name': 'test_parcours', 'time': 600, 'validate_condition': 10, 'id_code': parcours_id, 'exercices': [ + {'exercice_id': exo1['id_code'], 'quantity': 3, 'name': 'test1'}], + 'challenges': [challenge1, challenge2, challenge3].sort(key=lambda d: d['id_code'])} + + def test_update_parcours_auth(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) exo1 = test_create(name="test1", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) exo2 = test_create(name="test2", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) exo3 = test_create(name="test3", client=client, user={ - "token": token, "username": "lilian"}) + "token": token, "username": "lilian"}) exo4 = test_create(name="test4", client=client, user={ - "token": token, "username": "lilian"}) - + "token": token, "username": "lilian"}) + with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { "token": token}}) @@ -1083,44 +1271,55 @@ def test_update_parcours_auth(client: TestClient): {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() - r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": exo1['id_code'], "quantity": 3}]}}) - + r = client.post(f'/room/{room["room"]}/parcours', headers={"Authorization": "Bearer " + token}, json={ + "parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, "exercices": [ + {"exercice_id": exo1['id_code'], "quantity": 3}]}}) + parcours_id = r.json()['id_code'] memberws.receive_json() - - + r = client.get(url=f'/room/{room["room"]}/challenge/{parcours_id}', headers={"Authorization": "Bearer " + token2}) data = r.json()['challenge'] filled_obj = [ [{**e, 'inputs': [{"index": 0, "value": str(data[0].index(e))}]} for e in data[0]]] r = client.post( - f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) - challenge1 = {**stripKey(r.json(), "data"), "challenger": mdata['data']['member']['id_code'], "canCorrige": True} - - + f'/room/{room["room"]}/challenge/{parcours_id}/{r.json()["id_code"]}', + json={'challenge': filled_obj, "time": 162}, headers={"Authorization": "Bearer " + token2}) + challenge1 = {**stripKey(r.json(), "data"), + "challenger": mdata['data']['member']['id_code'], "canCorrige": True} + r = client.get(url=f'/room/{room["room"]}/parcours/{parcours_id}', headers={"Authorization": "Bearer " + token}) - assert {**r.json(), "challenges": r.json()['challenges'].sort(key=lambda d: d['id_code']) }== {'name': 'test_parcours', 'time': 600, 'validate_condition': 10, 'id_code': parcours_id, 'exercices': [{'exercice_id': exo1['id_code'], 'quantity': 3, 'name': 'test1'}], 'challenges': [challenge1].sort(key = lambda d: d['id_code'])} - - r = client.put(url=f'/room/{room["room"]}/parcours/{parcours_id}', json={"parcours": {"name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [ - {"exercice_id": "lolilol", "quantity": 12}, {"exercice_id": exo1['id_code'], "quantity": 10}, {"exercice_id": exo2['id_code'], "quantity": 5}, {"exercice_id": exo3['id_code'], "quantity": 12}]}}, headers={'Authorization': "Bearer " + token}) - - assert r.json() == {"id_code": parcours_id, "challenges": [], "name": "test_parcours", "time": 10*60, "validate_condition": 10, "exercices": [{ - "exercice_id": exo1['id_code'], "name": exo1['name'], "quantity": 10}, {"exercice_id": exo2['id_code'], "name": exo2['name'], "quantity": 5}, {"exercice_id": exo3['id_code'], "name": exo3['name'], "quantity": 12}]} + assert {**r.json(), "challenges": r.json()['challenges'].sort(key=lambda d: d['id_code'])} == { + 'name': 'test_parcours', 'time': 600, 'validate_condition': 10, + 'id_code': parcours_id, 'exercices': [{'exercice_id': exo1['id_code'], 'quantity': 3, 'name': 'test1'}], + 'challenges': [challenge1].sort(key=lambda d: d['id_code'])} + + r = client.put(url=f'/room/{room["room"]}/parcours/{parcours_id}', json={ + "parcours": {"name": "test_parcours", "time": 10 * 60, "validate_condition": 10, "exercices": [ + {"exercice_id": "lolilol", "quantity": 12}, {"exercice_id": exo1['id_code'], "quantity": 10}, + {"exercice_id": exo2['id_code'], "quantity": 5}, {"exercice_id": exo3['id_code'], "quantity": 12}]}}, + headers={'Authorization': "Bearer " + token}) + + assert r.json() == {"id_code": parcours_id, "challenges": [], "name": "test_parcours", "time": 10 * 60, + "validate_condition": 10, "exercices": [{ + "exercice_id": exo1['id_code'], "name": exo1['name'], "quantity": 10}, + {"exercice_id": exo2['id_code'], "name": exo2['name'], "quantity": 5}, + {"exercice_id": exo3['id_code'], "name": exo3['name'], "quantity": 12}]} admin.receive_json() admin.receive_json() admin.receive_json() adata = admin.receive_json() assert adata == {"type": "update_parcours", "data": {"parcours": { "name": "test_parcours", "id_code": parcours_id, "best_note": None}}} - + + def test_change_name(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) - + with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { "token": token}}) @@ -1130,19 +1329,20 @@ def test_change_name(client: TestClient): memberws.send_json( {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() - + admin.receive_json() admin.send_json({"type": "set_name", "data": {"name": "new_name"}}) adata = admin.receive_json() assert adata == {'type': "new_name", "data": {"name": "new_name"}} mdata = memberws.receive_json() assert mdata == {'type': "new_name", "data": {"name": "new_name"}} - + + def test_change_name_too_long(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) - + with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { "token": token}}) @@ -1152,20 +1352,20 @@ def test_change_name_too_long(client: TestClient): memberws.send_json( {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() - + admin.receive_json() - admin.send_json({"type": "set_name", "data": {"name": "new_name"*20}}) + admin.send_json( + {"type": "set_name", "data": {"name": "new_name" * 20}}) adata = admin.receive_json() assert adata == {'type': "error", "data": { "msg": "Nom trop long (max 20 character)"}} - - - + + def test_change_status(client: TestClient): token = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] room = test_create_room_auth(client=client, token=token, public=True) - + with client.websocket_connect(f"/ws/room/" + room['room']) as admin: admin.send_json({"type": "login", "data": { "token": token}}) @@ -1175,13 +1375,16 @@ def test_change_status(client: TestClient): memberws.send_json( {"type": "join", "data": {"token": token2}}) mdata = memberws.receive_json() - + admin.receive_json() - admin.send_json({"type": "set_visibility", "data": {"public": False}}) + admin.send_json({"type": "set_visibility", + "data": {"public": False}}) adata = admin.receive_json() - assert adata == {'type': "new_visibility", "data": {"public": False}} + assert adata == {'type': "new_visibility", + "data": {"public": False}} mdata = memberws.receive_json() - assert mdata == {'type': "new_visibility", "data": {"public": False}} + assert mdata == {'type': "new_visibility", + "data": {"public": False}} with client.websocket_connect(f"/ws/room/" + room['room']) as m2: m2.send_json( {"type": "join", "data": {"username": "okok"}}) @@ -1189,12 +1392,7 @@ def test_change_status(client: TestClient): assert "waiter_id" in mdata['data']['waiter'] assert mdata == {"type": "waiting", "data": {"waiter": { "username": "okok", "waiter_id": mdata['data']['waiter']['waiter_id']}}} - + adata = admin.receive_json() assert adata == {'type': "waiter", 'data': { "waiter": {"waiter_id": mdata['data']['waiter']['waiter_id'], "username": "okok"}}} - - - - - \ No newline at end of file diff --git a/backend/api/tests/testing_exo_source/exo_source_web_only.py b/backend/api/tests/testing_exo_source/exo_source_web_only.py index b28179d..2d46948 100644 --- a/backend/api/tests/testing_exo_source/exo_source_web_only.py +++ b/backend/api/tests/testing_exo_source/exo_source_web_only.py @@ -8,4 +8,4 @@ Fonction main() qui doit renvoyer un objet avec: def main(): t = random.randint(1, 10) - return {"csv": None, 'web': "None","pdf": None, "calcul": "1+1=2"} + return {"csv": None, 'web': "1 + [] = 2","pdf": None, "calcul": "1+1=2"} diff --git a/frontend/.idea/.gitignore b/frontend/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/frontend/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/frontend/.idea/dbnavigator.xml b/frontend/.idea/dbnavigator.xml new file mode 100644 index 0000000..9601314 --- /dev/null +++ b/frontend/.idea/dbnavigator.xml @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/.idea/frontend.iml b/frontend/.idea/frontend.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/frontend/.idea/frontend.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/frontend/.idea/inspectionProfiles/Project_Default.xml b/frontend/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/frontend/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/frontend/.idea/misc.xml b/frontend/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/frontend/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/.idea/modules.xml b/frontend/.idea/modules.xml new file mode 100644 index 0000000..f3d93d7 --- /dev/null +++ b/frontend/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/frontend/.idea/vcs.xml b/frontend/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/frontend/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts new file mode 100644 index 0000000..17161e3 --- /dev/null +++ b/frontend/cypress.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, + }, +}); diff --git a/frontend/cypress/e2e/exos.cy.ts b/frontend/cypress/e2e/exos.cy.ts new file mode 100644 index 0000000..d64d400 --- /dev/null +++ b/frontend/cypress/e2e/exos.cy.ts @@ -0,0 +1,7 @@ +// type definitions for Cypress object "cy" +/// +describe('test exo', ()=>{ + it('test exo', ()=>{ + cy.visit('localhost:5173') + }) +}) \ No newline at end of file diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/frontend/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts new file mode 100644 index 0000000..698b01a --- /dev/null +++ b/frontend/cypress/support/commands.ts @@ -0,0 +1,37 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } \ No newline at end of file diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts new file mode 100644 index 0000000..f80f74f --- /dev/null +++ b/frontend/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index e90595c..ff47b1f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "private": true, "scripts": { + "test": "npx vitest run --reporter verbose", "dev": "vite dev", "build": "vite build", "preview": "vite preview", @@ -14,12 +15,17 @@ "devDependencies": { "@sveltejs/adapter-auto": "^1.0.0", "@sveltejs/kit": "^1.0.0", + "@testing-library/svelte": "^3.2.2", "@types/chroma-js": "^2.1.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", + "@vitest/browser": "^0.28.4", + "@vitest/ui": "^0.28.4", + "cypress": "^12.6.0", "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte3": "^4.0.0", + "jsdom": "^21.1.0", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.8.1", "sass": "^1.53.0", @@ -28,21 +34,27 @@ "svelte-preprocess": "^4.10.7", "tslib": "^2.4.1", "typescript": "^4.9.3", - "vite": "^4.0.0" + "vite": "^4.0.0", + "vitest": "^0.28.4" }, "type": "module", "dependencies": { "@sveltestack/svelte-query": "^1.6.0", + "@testing-library/jest-dom": "^5.16.5", "@types/qs": "^6.9.7", "axios": "^1.2.2", "chroma-js": "^2.4.2", "jwt-decode": "^3.1.2", "qs": "^6.11.0", + "reconnecting-websocket": "^4.4.0", "svelecte": "^3.13.0", "svelte-forms": "^2.3.1", + "svelte-htm": "^1.2.0", "svelte-icons": "^2.1.0", + "svelte-markdown": "^0.2.3", "svelte-multiselect": "^8.2.3", "svelte-navigator": "^3.2.2", - "svelte-routing": "^1.6.0" + "svelte-routing": "^1.6.0", + "svelte-websocket-store": "^1.1.33" } } \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 5fb01a1..f01752d 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -4,56 +4,77 @@ specifiers: '@sveltejs/adapter-auto': ^1.0.0 '@sveltejs/kit': ^1.0.0 '@sveltestack/svelte-query': ^1.6.0 + '@testing-library/jest-dom': ^5.16.5 + '@testing-library/svelte': ^3.2.2 '@types/chroma-js': ^2.1.4 '@types/qs': ^6.9.7 '@typescript-eslint/eslint-plugin': ^5.45.0 '@typescript-eslint/parser': ^5.45.0 + '@vitest/browser': ^0.28.4 + '@vitest/ui': ^0.28.4 axios: ^1.2.2 chroma-js: ^2.4.2 + cypress: ^12.6.0 eslint: ^8.28.0 eslint-config-prettier: ^8.5.0 eslint-plugin-svelte3: ^4.0.0 + jsdom: ^21.1.0 jwt-decode: ^3.1.2 prettier: ^2.8.0 prettier-plugin-svelte: ^2.8.1 qs: ^6.11.0 + reconnecting-websocket: ^4.4.0 sass: ^1.53.0 svelecte: ^3.13.0 svelte: ^3.54.0 svelte-check: ^2.9.2 svelte-forms: ^2.3.1 + svelte-htm: ^1.2.0 svelte-icons: ^2.1.0 + svelte-markdown: ^0.2.3 svelte-multiselect: ^8.2.3 svelte-navigator: ^3.2.2 svelte-preprocess: ^4.10.7 svelte-routing: ^1.6.0 + svelte-websocket-store: ^1.1.33 tslib: ^2.4.1 typescript: ^4.9.3 vite: ^4.0.0 + vitest: ^0.28.4 dependencies: '@sveltestack/svelte-query': 1.6.0 + '@testing-library/jest-dom': 5.16.5 '@types/qs': 6.9.7 axios: 1.2.2 chroma-js: 2.4.2 jwt-decode: 3.1.2 qs: 6.11.0 + reconnecting-websocket: 4.4.0 svelecte: 3.13.0 svelte-forms: 2.3.1 + svelte-htm: 1.2.0_svelte@3.55.0 svelte-icons: 2.1.0 + svelte-markdown: 0.2.3_svelte@3.55.0 svelte-multiselect: 8.2.3 svelte-navigator: 3.2.2_niwyv7xychq2ag6arq5eqxbomm svelte-routing: 1.6.0_niwyv7xychq2ag6arq5eqxbomm + svelte-websocket-store: 1.1.33 devDependencies: '@sveltejs/adapter-auto': 1.0.0_@sveltejs+kit@1.0.1 '@sveltejs/kit': 1.0.1_svelte@3.55.0+vite@4.0.3 + '@testing-library/svelte': 3.2.2_svelte@3.55.0 '@types/chroma-js': 2.1.4 '@typescript-eslint/eslint-plugin': 5.47.1_txmweb6yn7coi7nfrp22gpyqmy '@typescript-eslint/parser': 5.47.1_lzzuuodtsqwxnvqeq4g4likcqa + '@vitest/browser': 0.28.4 + '@vitest/ui': 0.28.4 + cypress: 12.6.0 eslint: 8.30.0 eslint-config-prettier: 8.5.0_eslint@8.30.0 eslint-plugin-svelte3: 4.0.0_khrjkzzv5v2x7orkj5o7sxbz3a + jsdom: 21.1.0 prettier: 2.8.1 prettier-plugin-svelte: 2.9.0_ajxj753sv7dbwexjherrch25ta sass: 1.57.1 @@ -63,9 +84,86 @@ devDependencies: tslib: 2.4.1 typescript: 4.9.4 vite: 4.0.3_sass@1.57.1 + vitest: 0.28.4_x3jjsgulbh7575qaqkjiilw4p4 packages: + /@adobe/css-tools/4.1.0: + resolution: {integrity: sha512-mMVJ/j/GbZ/De4ZHWbQAQO1J6iVnjtZLc9WEdkUQb8S/Bu2cAF2bETXUgMAdvMG3/ngtKmcNBe+Zms9bg6jnQQ==} + dev: false + + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + + /@babel/runtime-corejs3/7.20.13: + resolution: {integrity: sha512-p39/6rmY9uvlzRiLZBIB3G9/EBr66LBMcYm7fIDeSBNdRjF2AGD3rFZucUyAgGHC2N+7DdLvVi33uTjSE44FIw==} + engines: {node: '>=6.9.0'} + dependencies: + core-js-pure: 3.28.0 + regenerator-runtime: 0.13.11 + dev: false + + /@babel/runtime/7.20.13: + resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + + /@colors/colors/1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + requiresBuild: true + dev: true + optional: true + + /@cypress/request/2.88.11: + resolution: {integrity: sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==} + engines: {node: '>= 6'} + dependencies: + aws-sign2: 0.7.0 + aws4: 1.12.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + http-signature: 1.3.6 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + performance-now: 2.1.0 + qs: 6.10.4 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 8.3.2 + dev: true + + /@cypress/xvfb/1.2.4_supports-color@8.1.1: + resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} + dependencies: + debug: 3.2.7_supports-color@8.1.1 + lodash.once: 4.1.1 + transitivePeerDependencies: + - supports-color + dev: true + /@esbuild/android-arm/0.16.11: resolution: {integrity: sha512-j2xsG1OETgCe+OBA54DG5vLuGjmMZtQvyxt+rTw2aYK/RqjcG/F+UDdj43uoUOv8lSRC3lM4XpKLOVZfY/82yA==} engines: {node: '>=12'} @@ -156,6 +254,15 @@ packages: dev: true optional: true + /@esbuild/linux-loong64/0.14.54: + resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-loong64/0.16.11: resolution: {integrity: sha512-2MCYdDBh9R+R1xuBFiApgkbp/tW1uV+aVeefKYqWSEk3o6MHzWo1FxEGA4dSnC+kThSBOMVpCV9z4/DPouA3bQ==} engines: {node: '>=12'} @@ -301,6 +408,32 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@jest/expect-utils/29.4.2: + resolution: {integrity: sha512-Dd3ilDJpBnqa0GiPN7QrudVs0cczMMHtehSo2CSTjm3zdHx0RcpmhFNVEltuEFeqfLIyWKFI224FsMSQ/nsJQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.4.2 + dev: false + + /@jest/schemas/29.4.2: + resolution: {integrity: sha512-ZrGzGfh31NtdVH8tn0mgJw4khQuNHiKqdzJAFbCaERbyCP9tHlxWuL/mnMu8P7e/+k4puWjI1NOzi/sFsjce/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.25.21 + dev: false + + /@jest/types/29.4.2: + resolution: {integrity: sha512-CKlngyGP0fwlgC1BRUtPZSiWLBhyS9dKwKmyGxk8Z6M82LBEGB2aLQSg+U1MyLsU+M7UjnlLllBM2BLWKVm/Uw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.4.2 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.11.18 + '@types/yargs': 17.0.22 + chalk: 4.1.2 + dev: false + /@jridgewell/resolve-uri/3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} @@ -317,6 +450,10 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@jspm/core/2.0.0-beta.24: + resolution: {integrity: sha512-a4Bo/80Z6CoJNor5ldgs6002utmmbttP4JYd/FJ0Ob2fVdf6O6ha5SORBCqrnDnBvMc1TlrHY7dCfat5+H0a6A==} + dev: true + /@nodelib/fs.scandir/2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -342,6 +479,33 @@ packages: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true + /@rollup/plugin-inject/4.0.4_rollup@2.79.1: + resolution: {integrity: sha512-4pbcU4J/nS+zuHk+c+OL3WtmEQhqxlZ9uqfjQMQDOHOPld7PsCd8k5LWs8h5wjwJN7MgnAn768F2sDxEP4eNFQ==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + estree-walker: 2.0.2 + magic-string: 0.25.9 + rollup: 2.79.1 + dev: true + + /@rollup/pluginutils/3.1.0_rollup@2.79.1: + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.1 + dev: true + + /@sinclair/typebox/0.25.21: + resolution: {integrity: sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==} + dev: false + /@sveltejs/adapter-auto/1.0.0_@sveltejs+kit@1.0.1: resolution: {integrity: sha512-yKyPvlLVua1bJ/42FrR3X041mFGdB4GzTZOAEoHUcNBRE5Mhx94+eqHpC3hNvAOiLEDcKfVO0ObyKSu7qldU+w==} peerDependencies: @@ -407,6 +571,64 @@ packages: optional: true dev: false + /@testing-library/dom/8.20.0: + resolution: {integrity: sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==} + engines: {node: '>=12'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/runtime': 7.20.13 + '@types/aria-query': 5.0.1 + aria-query: 5.1.3 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.4.4 + pretty-format: 27.5.1 + dev: true + + /@testing-library/jest-dom/5.16.5: + resolution: {integrity: sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==} + engines: {node: '>=8', npm: '>=6', yarn: '>=1'} + dependencies: + '@adobe/css-tools': 4.1.0 + '@babel/runtime': 7.20.13 + '@types/testing-library__jest-dom': 5.14.5 + aria-query: 5.1.3 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.5.16 + lodash: 4.17.21 + redent: 3.0.0 + dev: false + + /@testing-library/svelte/3.2.2_svelte@3.55.0: + resolution: {integrity: sha512-IKwZgqbekC3LpoRhSwhd0JswRGxKdAGkf39UiDXTywK61YyLXbCYoR831e/UUC6EeNW4hiHPY+2WuovxOgI5sw==} + engines: {node: '>= 10'} + peerDependencies: + svelte: 3.x + dependencies: + '@testing-library/dom': 8.20.0 + svelte: 3.55.0 + dev: true + + /@tootallnate/once/2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: true + + /@types/aria-query/5.0.1: + resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} + dev: true + + /@types/chai-subset/1.3.3: + resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + dependencies: + '@types/chai': 4.3.4 + dev: true + + /@types/chai/4.3.4: + resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} + dev: true + /@types/chroma-js/2.1.4: resolution: {integrity: sha512-l9hWzP7cp7yleJUI7P2acmpllTJNYf5uU6wh50JzSIZt3fFHe+w2FM6w9oZGBTYzjjm2qHdnQvI+fF/JF/E5jQ==} dev: true @@ -415,13 +637,47 @@ packages: resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==} dev: true + /@types/estree/0.0.39: + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + dev: true + + /@types/istanbul-lib-coverage/2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + dev: false + + /@types/istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + dev: false + + /@types/istanbul-reports/3.0.1: + resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + dependencies: + '@types/istanbul-lib-report': 3.0.0 + dev: false + + /@types/jest/29.4.0: + resolution: {integrity: sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==} + dependencies: + expect: 29.4.2 + pretty-format: 29.4.2 + dev: false + /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true + /@types/marked/4.0.8: + resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==} + dev: false + + /@types/node/14.18.36: + resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==} + dev: true + /@types/node/18.11.18: resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} - dev: true /@types/pug/2.0.6: resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} @@ -441,6 +697,42 @@ packages: resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} dev: true + /@types/sinonjs__fake-timers/8.1.1: + resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} + dev: true + + /@types/sizzle/2.3.3: + resolution: {integrity: sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==} + dev: true + + /@types/stack-utils/2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + dev: false + + /@types/testing-library__jest-dom/5.14.5: + resolution: {integrity: sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==} + dependencies: + '@types/jest': 29.4.0 + dev: false + + /@types/yargs-parser/21.0.0: + resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + dev: false + + /@types/yargs/17.0.22: + resolution: {integrity: sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: false + + /@types/yauzl/2.10.0: + resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} + requiresBuild: true + dependencies: + '@types/node': 18.11.18 + dev: true + optional: true + /@typescript-eslint/eslint-plugin/5.47.1_txmweb6yn7coi7nfrp22gpyqmy: resolution: {integrity: sha512-r4RZ2Jl9kcQN7K/dcOT+J7NAimbiis4sSM9spvWimsBvDegMhKLA5vri2jG19PmIPbDjPeWzfUPQ2hjEzA4Nmg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -570,6 +862,70 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /@vitest/browser/0.28.4: + resolution: {integrity: sha512-jIbiVf2VBIvOx1kkLoTU+ehqNM0m932lO3ZvbOqqR3FHbbxUCWTrkOyFwQRFMfbpVJ9oVAsK8naccIGj1aPeMQ==} + dependencies: + '@vitest/runner': 0.28.4 + local-pkg: 0.4.3 + mlly: 1.1.0 + modern-node-polyfills: 0.0.9 + rollup-plugin-node-polyfills: 0.2.1 + sirv: 2.0.2 + dev: true + + /@vitest/expect/0.28.4: + resolution: {integrity: sha512-JqK0NZ4brjvOSL8hXAnIsfi+jxDF7rH/ZWCGCt0FAqRnVFc1hXsfwXksQvEnKqD84avRt3gmeXoK4tNbmkoVsQ==} + dependencies: + '@vitest/spy': 0.28.4 + '@vitest/utils': 0.28.4 + chai: 4.3.7 + dev: true + + /@vitest/runner/0.28.4: + resolution: {integrity: sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==} + dependencies: + '@vitest/utils': 0.28.4 + p-limit: 4.0.0 + pathe: 1.1.0 + dev: true + + /@vitest/spy/0.28.4: + resolution: {integrity: sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==} + dependencies: + tinyspy: 1.1.1 + dev: true + + /@vitest/ui/0.28.4: + resolution: {integrity: sha512-LQfCCFc17n49mwtraV9/NAWl2DUqJS/9ZEa3fqJjoYO+HowdseQ5jvWflpzliCyfrIAh6cXVo1bNzHnDXe0cbw==} + dependencies: + fast-glob: 3.2.12 + flatted: 3.2.7 + pathe: 1.1.0 + picocolors: 1.0.0 + sirv: 2.0.2 + dev: true + + /@vitest/utils/0.28.4: + resolution: {integrity: sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==} + dependencies: + cli-truncate: 3.1.0 + diff: 5.1.0 + loupe: 2.3.6 + picocolors: 1.0.0 + pretty-format: 27.5.1 + dev: true + + /abab/2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + dev: true + + /acorn-globals/7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + dependencies: + acorn: 8.8.2 + acorn-walk: 8.2.0 + dev: true + /acorn-jsx/5.3.2_acorn@8.8.1: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -578,12 +934,40 @@ packages: acorn: 8.8.1 dev: true + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn/8.8.1: resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} engines: {node: '>=0.4.0'} hasBin: true dev: true + /acorn/8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /agent-base/6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /aggregate-error/3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + /ajv/6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -593,16 +977,47 @@ packages: uri-js: 4.4.1 dev: true + /ansi-colors/4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true + + /ansi-escapes/4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + /ansi-regex/5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} dev: true + /ansi-regex/6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + /ansi-styles/4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} dependencies: color-convert: 2.0.1 + + /ansi-styles/5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + /ansi-styles/6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} dev: true /anymatch/3.1.3: @@ -613,18 +1028,67 @@ packages: picomatch: 2.3.1 dev: true + /arch/2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + dev: true + /argparse/2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /aria-query/5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + dependencies: + deep-equal: 2.2.0 + /array-union/2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} dev: true + /asn1/0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + dependencies: + safer-buffer: 2.1.2 + dev: true + + /assert-plus/1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + dev: true + + /assertion-error/1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /astral-regex/2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + dev: true + + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: true + /asynckit/0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: false + + /at-least-node/1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + dev: true + + /available-typed-arrays/1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + + /aws-sign2/0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + dev: true + + /aws4/1.12.0: + resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==} + dev: true /axios/1.2.2: resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==} @@ -640,11 +1104,29 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true + /base64-js/1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /bcrypt-pbkdf/1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + dependencies: + tweetnacl: 0.14.5 + dev: true + /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} dev: true + /blob-util/2.0.2: + resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} + dev: true + + /bluebird/3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + dev: true + /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -657,12 +1139,22 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - dev: true /buffer-crc32/0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: true + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /buffer/5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + /busboy/1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -670,24 +1162,74 @@ packages: streamsearch: 1.1.0 dev: true + /cac/6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + + /cachedir/2.3.0: + resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} + engines: {node: '>=6'} + dev: true + /call-bind/1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: function-bind: 1.1.1 - get-intrinsic: 1.1.3 - dev: false + get-intrinsic: 1.2.0 /callsites/3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} dev: true + /caseless/0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: true + + /chai/4.3.7: + resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.2 + deep-eql: 4.1.3 + get-func-name: 2.0.0 + loupe: 2.3.6 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + /chalk/3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + + /check-error/1.0.2: + resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} + dev: true + + /check-more-types/2.24.0: + resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} + engines: {node: '>= 0.8.0'} dev: true /chokidar/3.5.3: @@ -709,15 +1251,66 @@ packages: resolution: {integrity: sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==} dev: false + /ci-info/3.8.0: + resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} + engines: {node: '>=8'} + + /clean-stack/2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + + /cli-cursor/3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-table3/0.6.3: + resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} + engines: {node: 10.* || >= 12.*} + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + dev: true + + /cli-truncate/2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + dev: true + + /cli-truncate/3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + dev: true + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + /color-convert/2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true + + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /colorette/2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: true /combined-stream/1.0.8: @@ -725,7 +1318,16 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: false + + /commander/5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + dev: true + + /common-tags/1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + dev: true /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -736,6 +1338,20 @@ packages: engines: {node: '>= 0.6'} dev: true + /core-js-pure/3.28.0: + resolution: {integrity: sha512-DSOVleA9/v3LNj/vFxAPfUHttKTzrB2RXhAPvR5TPXn4vrra3Z2ssytvRyt8eruJwAfwAiFADEbrjcRdcvPLQQ==} + requiresBuild: true + dev: false + + /core-js/3.28.0: + resolution: {integrity: sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==} + requiresBuild: true + dev: false + + /core-util-is/1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + dev: true + /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -745,6 +1361,107 @@ packages: which: 2.0.2 dev: true + /css.escape/1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + dev: false + + /cssom/0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + dev: true + + /cssom/0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + dev: true + + /cssstyle/2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + dependencies: + cssom: 0.3.8 + dev: true + + /cypress/12.6.0: + resolution: {integrity: sha512-WdHSVaS1lumSd5XpVTslZd8ui9GIGphrzvXq9+3DtVhqjRZC5M70gu5SW/Y/SLPq3D1wiXGZoHC6HJ7ESVE2lw==} + engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0} + hasBin: true + requiresBuild: true + dependencies: + '@cypress/request': 2.88.11 + '@cypress/xvfb': 1.2.4_supports-color@8.1.1 + '@types/node': 14.18.36 + '@types/sinonjs__fake-timers': 8.1.1 + '@types/sizzle': 2.3.3 + arch: 2.2.0 + blob-util: 2.0.2 + bluebird: 3.7.2 + buffer: 5.7.1 + cachedir: 2.3.0 + chalk: 4.1.2 + check-more-types: 2.24.0 + cli-cursor: 3.1.0 + cli-table3: 0.6.3 + commander: 5.1.0 + common-tags: 1.8.2 + dayjs: 1.11.7 + debug: 4.3.4_supports-color@8.1.1 + enquirer: 2.3.6 + eventemitter2: 6.4.7 + execa: 4.1.0 + executable: 4.1.1 + extract-zip: 2.0.1_supports-color@8.1.1 + figures: 3.2.0 + fs-extra: 9.1.0 + getos: 3.2.1 + is-ci: 3.0.1 + is-installed-globally: 0.4.0 + lazy-ass: 1.6.0 + listr2: 3.14.0_enquirer@2.3.6 + lodash: 4.17.21 + log-symbols: 4.1.0 + minimist: 1.2.7 + ospath: 1.2.2 + pretty-bytes: 5.6.0 + proxy-from-env: 1.0.0 + request-progress: 3.0.0 + semver: 7.3.8 + supports-color: 8.1.1 + tmp: 0.2.1 + untildify: 4.0.0 + yauzl: 2.10.0 + dev: true + + /dashdash/1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + dependencies: + assert-plus: 1.0.0 + dev: true + + /data-urls/3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + dev: true + + /dayjs/1.11.7: + resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} + dev: true + + /debug/3.2.7_supports-color@8.1.1: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + dev: true + /debug/4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -757,10 +1474,55 @@ packages: ms: 2.1.2 dev: true + /debug/4.3.4_supports-color@8.1.1: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + dev: true + + /decimal.js/10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dev: true + /dedent-js/1.0.1: resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} dev: false + /deep-eql/4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-equal/2.2.0: + resolution: {integrity: sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==} + dependencies: + call-bind: 1.0.2 + es-get-iterator: 1.1.3 + get-intrinsic: 1.2.0 + is-arguments: 1.1.1 + is-array-buffer: 3.0.1 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + isarray: 2.0.5 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.9 + /deep-is/0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -770,10 +1532,16 @@ packages: engines: {node: '>=0.10.0'} dev: true + /define-properties/1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + /delayed-stream/1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: false /detect-indent/6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} @@ -784,6 +1552,16 @@ packages: resolution: {integrity: sha512-mbjoAaCL2qogBKgeFxFPOXAUsZchircF+B/79LD4sHH0+NHfYm8gZpQrskKDn5gENGt35+5OI1GUF7hLVnkPDw==} dev: true + /diff-sequences/29.4.2: + resolution: {integrity: sha512-R6P0Y6PrsH3n4hUXxL3nns0rbRk6Q33js3ygJBeEpbzLzgcNuJ61+u0RXasFpTKISw99TxUzFnumSnRLsjhLaw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: false + + /diff/5.1.0: + resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + engines: {node: '>=0.3.1'} + dev: true + /dir-glob/3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -798,10 +1576,279 @@ packages: esutils: 2.0.3 dev: true + /dom-accessibility-api/0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + /domexception/4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + dependencies: + webidl-conversions: 7.0.0 + dev: true + + /eastasianwidth/0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + + /ecc-jsbn/0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + dev: true + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /emoji-regex/9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + + /end-of-stream/1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: true + + /enquirer/2.3.6: + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} + dependencies: + ansi-colors: 4.1.3 + dev: true + + /entities/4.4.0: + resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + engines: {node: '>=0.12'} + dev: true + + /es-get-iterator/1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.2 + is-set: 2.0.2 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 + /es6-promise/3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} dev: true + /esbuild-android-64/0.14.54: + resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-android-arm64/0.14.54: + resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64/0.14.54: + resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64/0.14.54: + resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64/0.14.54: + resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64/0.14.54: + resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32/0.14.54: + resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64/0.14.54: + resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm/0.14.54: + resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64/0.14.54: + resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le/0.14.54: + resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le/0.14.54: + resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64/0.14.54: + resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x/0.14.54: + resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64/0.14.54: + resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-openbsd-64/0.14.54: + resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-sunos-64/0.14.54: + resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32/0.14.54: + resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64/0.14.54: + resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64/0.14.54: + resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild/0.14.54: + resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/linux-loong64': 0.14.54 + esbuild-android-64: 0.14.54 + esbuild-android-arm64: 0.14.54 + esbuild-darwin-64: 0.14.54 + esbuild-darwin-arm64: 0.14.54 + esbuild-freebsd-64: 0.14.54 + esbuild-freebsd-arm64: 0.14.54 + esbuild-linux-32: 0.14.54 + esbuild-linux-64: 0.14.54 + esbuild-linux-arm: 0.14.54 + esbuild-linux-arm64: 0.14.54 + esbuild-linux-mips64le: 0.14.54 + esbuild-linux-ppc64le: 0.14.54 + esbuild-linux-riscv64: 0.14.54 + esbuild-linux-s390x: 0.14.54 + esbuild-netbsd-64: 0.14.54 + esbuild-openbsd-64: 0.14.54 + esbuild-sunos-64: 0.14.54 + esbuild-windows-32: 0.14.54 + esbuild-windows-64: 0.14.54 + esbuild-windows-arm64: 0.14.54 + dev: true + /esbuild/0.16.11: resolution: {integrity: sha512-Al0hhRUz/cCDvDp9VZp1L500HZZQ/HLjgTnQTmnW97+PoLmw+PuvB3e19JHYZtWnrxoh3qYrN/0tiRIbrE2oVQ==} engines: {node: '>=12'} @@ -832,11 +1879,33 @@ packages: '@esbuild/win32-x64': 0.16.11 dev: true + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + /escape-string-regexp/2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: false + /escape-string-regexp/4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} dev: true + /escodegen/2.0.0: + resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + /eslint-config-prettier/8.5.0_eslint@8.30.0: resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} hasBin: true @@ -953,6 +2022,12 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /esprima/4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + /esquery/1.4.0: resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} engines: {node: '>=0.10'} @@ -977,11 +2052,83 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-walker/0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + dev: true + + /estree-walker/1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + dev: true + + /estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} dev: true + /eventemitter2/6.4.7: + resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} + dev: true + + /execa/4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /executable/4.1.1: + resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} + engines: {node: '>=4'} + dependencies: + pify: 2.3.0 + dev: true + + /expect/29.4.2: + resolution: {integrity: sha512-+JHYg9O3hd3RlICG90OPVjRkPBoiUH7PxvDVMnRiaq1g6JUgZStX514erMl0v2Dc5SkfVbm7ztqbd6qHHPn+mQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/expect-utils': 29.4.2 + jest-get-type: 29.4.2 + jest-matcher-utils: 29.4.2 + jest-message-util: 29.4.2 + jest-util: 29.4.2 + dev: false + + /extend/3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: true + + /extract-zip/2.0.1_supports-color@8.1.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + dependencies: + debug: 4.3.4_supports-color@8.1.1 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.0 + transitivePeerDependencies: + - supports-color + dev: true + + /extsprintf/1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + dev: true + /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1011,6 +2158,19 @@ packages: reusify: 1.0.4 dev: true + /fd-slicer/1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + dependencies: + pend: 1.2.0 + dev: true + + /figures/3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + /file-entry-cache/6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1023,7 +2183,6 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - dev: true /find-up/5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} @@ -1055,6 +2214,24 @@ packages: optional: true dev: false + /for-each/0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + + /forever-agent/0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + dev: true + + /form-data/2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true + /form-data/4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -1062,7 +2239,16 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: false + + /fs-extra/9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1079,13 +2265,38 @@ packages: /function-bind/1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - /get-intrinsic/1.1.3: - resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} + /functions-have-names/1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + /get-func-name/2.0.0: + resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} + dev: true + + /get-intrinsic/1.2.0: + resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} dependencies: function-bind: 1.1.1 has: 1.0.3 has-symbols: 1.0.3 - dev: false + + /get-stream/5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.0 + dev: true + + /getos/3.2.1: + resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} + dependencies: + async: 3.2.4 + dev: true + + /getpass/0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + dependencies: + assert-plus: 1.0.0 + dev: true /glob-parent/5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -1112,6 +2323,13 @@ packages: path-is-absolute: 1.0.1 dev: true + /global-dirs/3.0.1: + resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} + engines: {node: '>=10'} + dependencies: + ini: 2.0.0 + dev: true + /globals/13.19.0: resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==} engines: {node: '>=8'} @@ -1139,23 +2357,43 @@ packages: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} dev: true + /gopd/1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.0 + /graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - dev: true /grapheme-splitter/1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /has-bigints/1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + /has-flag/4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true + + /has-property-descriptors/1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.0 /has-symbols/1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - dev: false + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} @@ -1163,6 +2401,63 @@ packages: dependencies: function-bind: 1.1.1 + /htm/3.1.1: + resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==} + dev: false + + /html-encoding-sniffer/3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + dependencies: + whatwg-encoding: 2.0.0 + dev: true + + /http-proxy-agent/5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /http-signature/1.3.6: + resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} + engines: {node: '>=0.10'} + dependencies: + assert-plus: 1.0.0 + jsprim: 2.0.2 + sshpk: 1.17.0 + dev: true + + /https-proxy-agent/5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /human-signals/1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + dev: true + + /iconv-lite/0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + + /ieee754/1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + /ignore/5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -1189,6 +2484,10 @@ packages: engines: {node: '>=0.8.19'} dev: true + /indent-string/4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + /inflight/1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -1200,6 +2499,38 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true + /ini/2.0.0: + resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} + engines: {node: '>=10'} + dev: true + + /internal-slot/1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.0 + has: 1.0.3 + side-channel: 1.0.4 + + /is-arguments/1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-array-buffer/3.0.1: + resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + is-typed-array: 1.1.10 + + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + /is-binary-path/2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1207,17 +2538,51 @@ packages: binary-extensions: 2.2.0 dev: true + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-callable/1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + /is-ci/3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + dependencies: + ci-info: 3.8.0 + dev: true + /is-core-module/2.11.0: resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} dependencies: has: 1.0.3 dev: true + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + /is-extglob/2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} dev: true + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-fullwidth-code-point/4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true + /is-glob/4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1225,28 +2590,170 @@ packages: is-extglob: 2.1.1 dev: true + /is-installed-globally/0.4.0: + resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} + engines: {node: '>=10'} + dependencies: + global-dirs: 3.0.1 + is-path-inside: 3.0.3 + dev: true + + /is-map/2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + /is-number/7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true /is-path-inside/3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} dev: true + /is-potential-custom-element-name/1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: true + /is-promise/4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} dev: false + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-set/2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + + /is-stream/2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + + /is-typed-array/1.1.10: + resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + + /is-typedarray/1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + dev: true + + /is-unicode-supported/0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /is-weakmap/2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + + /is-weakset/2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + + /isarray/2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + /isexe/2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /isstream/0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + dev: true + + /jest-diff/29.4.2: + resolution: {integrity: sha512-EK8DSajVtnjx9sa1BkjZq3mqChm2Cd8rIzdXkQMA8e0wuXq53ypz6s5o5V8HRZkoEt2ywJ3eeNWFKWeYr8HK4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 29.4.2 + jest-get-type: 29.4.2 + pretty-format: 29.4.2 + dev: false + + /jest-get-type/29.4.2: + resolution: {integrity: sha512-vERN30V5i2N6lqlFu4ljdTqQAgrkTFMC9xaIIfOPYBw04pufjXRty5RuXBiB1d72tGbURa/UgoiHB90ruOSivg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: false + + /jest-matcher-utils/29.4.2: + resolution: {integrity: sha512-EZaAQy2je6Uqkrm6frnxBIdaWtSYFoR8SVb2sNLAtldswlR/29JAgx+hy67llT3+hXBaLB0zAm5UfeqerioZyg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 29.4.2 + jest-get-type: 29.4.2 + pretty-format: 29.4.2 + dev: false + + /jest-message-util/29.4.2: + resolution: {integrity: sha512-SElcuN4s6PNKpOEtTInjOAA8QvItu0iugkXqhYyguRvQoXapg5gN+9RQxLAkakChZA7Y26j6yUCsFWN+hlKD6g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.18.6 + '@jest/types': 29.4.2 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + micromatch: 4.0.5 + pretty-format: 29.4.2 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: false + + /jest-util/29.4.2: + resolution: {integrity: sha512-wKnm6XpJgzMUSRFB7YF48CuwdzuDIHenVuoIb1PLuJ6F+uErZsuDkU+EiExkChf6473XcawBrSfDSnXl+/YG4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.4.2 + '@types/node': 18.11.18 + chalk: 4.1.2 + ci-info: 3.8.0 + graceful-fs: 4.2.10 + picomatch: 2.3.1 + dev: false + /js-sdsl/4.2.0: resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} dev: true + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + /js-yaml/4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1254,14 +2761,89 @@ packages: argparse: 2.0.1 dev: true + /jsbn/0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + dev: true + + /jsdom/21.1.0: + resolution: {integrity: sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + abab: 2.0.6 + acorn: 8.8.2 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.4.3 + domexception: 4.0.0 + escodegen: 2.0.0 + form-data: 4.0.0 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.2 + parse5: 7.1.2 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.2 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.12.0 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + /json-schema-traverse/0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true + /json-schema/0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + dev: true + /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /json-stringify-safe/5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + + /jsonc-parser/3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /jsonfile/6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.10 + dev: true + + /jsprim/2.0.2: + resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + dev: true + /jwt-decode/3.1.2: resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} dev: false @@ -1271,6 +2853,19 @@ packages: engines: {node: '>=6'} dev: true + /lazy-ass/1.6.0: + resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} + engines: {node: '> 0.8'} + dev: true + + /levn/0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + dev: true + /levn/0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1279,6 +2874,31 @@ packages: type-check: 0.4.0 dev: true + /listr2/3.14.0_enquirer@2.3.6: + resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} + engines: {node: '>=10.0.0'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.19 + enquirer: 2.3.6 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.3.0 + rxjs: 7.8.0 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + + /local-pkg/0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + engines: {node: '>=14'} + dev: true + /locate-path/6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1290,6 +2910,37 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.once/4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: true + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + /log-symbols/4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /log-update/4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + dev: true + + /loupe/2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + dependencies: + get-func-name: 2.0.0 + dev: true + /lower-case/2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: @@ -1303,6 +2954,11 @@ packages: yallist: 4.0.0 dev: true + /lz-string/1.4.4: + resolution: {integrity: sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==} + hasBin: true + dev: true + /magic-string/0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} dependencies: @@ -1316,6 +2972,16 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /marked/4.2.12: + resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} + engines: {node: '>= 12'} + hasBin: true + dev: false + + /merge-stream/2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + /merge2/1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1327,19 +2993,16 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.1 - dev: true /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: false /mime-types/2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: false /mime/3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} @@ -1347,10 +3010,14 @@ packages: hasBin: true dev: true + /mimic-fn/2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + /min-indent/1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - dev: true /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1369,6 +3036,26 @@ packages: minimist: 1.2.7 dev: true + /mlly/1.1.0: + resolution: {integrity: sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==} + dependencies: + acorn: 8.8.2 + pathe: 1.1.0 + pkg-types: 1.0.1 + ufo: 1.0.1 + dev: true + + /modern-node-polyfills/0.0.9: + resolution: {integrity: sha512-Z7sFaWAHCEAw44Ww1L0JEt4BaQ7/LVTbbqTtm3bNSfdWs0ZW7QwRN7Xy8RjSPOlZ9ZSkoXAa54neuvAC6KGRFg==} + dependencies: + '@jspm/core': 2.0.0-beta.24 + '@rollup/plugin-inject': 4.0.4_rollup@2.79.1 + acorn: 8.8.2 + esbuild: 0.14.54 + local-pkg: 0.4.3 + rollup: 2.79.1 + dev: true + /mri/1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1409,9 +3096,39 @@ packages: engines: {node: '>=0.10.0'} dev: true + /npm-run-path/4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + + /nwsapi/2.2.2: + resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==} + dev: true + /object-inspect/1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} - dev: false + + /object-is/1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + /object.assign/4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + has-symbols: 1.0.3 + object-keys: 1.1.1 /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1419,6 +3136,25 @@ packages: wrappy: 1.0.2 dev: true + /onetime/5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /optionator/0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.3 + dev: true + /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -1431,6 +3167,10 @@ packages: word-wrap: 1.2.3 dev: true + /ospath/1.2.2: + resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} + dev: true + /p-limit/3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1438,6 +3178,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-limit/4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate/5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -1445,6 +3192,13 @@ packages: p-limit: 3.1.0 dev: true + /p-map/4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + dependencies: + aggregate-error: 3.1.0 + dev: true + /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1452,6 +3206,12 @@ packages: callsites: 3.1.0 dev: true + /parse5/7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.4.0 + dev: true + /pascal-case/3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: @@ -1483,6 +3243,22 @@ packages: engines: {node: '>=8'} dev: true + /pathe/1.1.0: + resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + dev: true + + /pathval/1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /pend/1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: true + + /performance-now/2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + dev: true + /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -1490,6 +3266,18 @@ packages: /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + + /pify/2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true + + /pkg-types/1.0.1: + resolution: {integrity: sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.1.0 + pathe: 1.1.0 dev: true /postcss/8.4.20: @@ -1501,6 +3289,11 @@ packages: source-map-js: 1.0.2 dev: true + /prelude-ls/1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + dev: true + /prelude-ls/1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1522,15 +3315,60 @@ packages: hasBin: true dev: true + /pretty-bytes/5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + dev: true + + /pretty-format/27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: true + + /pretty-format/29.4.2: + resolution: {integrity: sha512-qKlHR8yFVCbcEWba0H0TOC8dnLlO4vPlyEjRPw31FZ2Rupy9nLa8ZLbYny8gWEl8CkEhJqAE6IzdNELTBVcBEg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.4.2 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: false + + /proxy-from-env/1.0.0: + resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} + dev: true + /proxy-from-env/1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false + /psl/1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: true + + /pump/3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} dev: true + /qs/6.10.4: + resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: true + /qs/6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} @@ -1538,10 +3376,22 @@ packages: side-channel: 1.0.4 dev: false + /querystringify/2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true + /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /react-is/17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: true + + /react-is/18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: false + /readdirp/3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -1549,11 +3399,44 @@ packages: picomatch: 2.3.1 dev: true + /reconnecting-websocket/4.4.0: + resolution: {integrity: sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==} + dev: false + + /redent/3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + dev: false + + /regenerator-runtime/0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + /regexp.prototype.flags/1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + functions-have-names: 1.2.3 + /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} dev: true + /request-progress/3.0.0: + resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} + dependencies: + throttleit: 1.0.0 + dev: true + + /requires-port/1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true + /resolve-from/4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1568,11 +3451,23 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /restore-cursor/3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true + /rfdc/1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + dev: true + /rimraf/2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true @@ -1587,6 +3482,35 @@ packages: glob: 7.2.3 dev: true + /rollup-plugin-inject/3.0.2: + resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. + dependencies: + estree-walker: 0.6.1 + magic-string: 0.25.9 + rollup-pluginutils: 2.8.2 + dev: true + + /rollup-plugin-node-polyfills/0.2.1: + resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} + dependencies: + rollup-plugin-inject: 3.0.2 + dev: true + + /rollup-pluginutils/2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + dependencies: + estree-walker: 0.6.1 + dev: true + + /rollup/2.79.1: + resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + /rollup/3.8.1: resolution: {integrity: sha512-4yh9eMW7byOroYcN8DlF9P/2jCpu6txVIHjEqquQVSx7DI0RgyCCN3tjrcy4ra6yVtV336aLBB3v2AarYAxePQ==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -1601,6 +3525,12 @@ packages: queue-microtask: 1.2.3 dev: true + /rxjs/7.8.0: + resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + dependencies: + tslib: 2.4.1 + dev: true + /sade/1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -1608,6 +3538,14 @@ packages: mri: 1.2.0 dev: true + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true + /sander/0.5.1: resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} dependencies: @@ -1627,6 +3565,13 @@ packages: source-map-js: 1.0.2 dev: true + /saxes/6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + dependencies: + xmlchars: 2.2.0 + dev: true + /semver/7.3.8: resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} engines: {node: '>=10'} @@ -1655,9 +3600,16 @@ packages: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: call-bind: 1.0.2 - get-intrinsic: 1.1.3 + get-intrinsic: 1.2.0 object-inspect: 1.12.2 - dev: false + + /siginfo/2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + + /signal-exit/3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true /sirv/2.0.2: resolution: {integrity: sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==} @@ -1671,6 +3623,31 @@ packages: /slash/3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + + /slice-ansi/3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + + /slice-ansi/4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + + /slice-ansi/5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 dev: true /sorcery/0.10.0: @@ -1688,16 +3665,83 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map-support/0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + /sourcemap-codec/1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead dev: true + /sshpk/1.17.0: + resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + dev: true + + /stack-utils/2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + dev: false + + /stackback/0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env/3.3.2: + resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} + dev: true + + /stop-iteration-iterator/1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} + dependencies: + internal-slot: 1.0.5 + /streamsearch/1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} dev: true + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string-width/5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.0.1 + dev: true + /strip-ansi/6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1705,23 +3749,52 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-ansi/7.0.1: + resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + + /strip-final-newline/2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true + /strip-indent/3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} dependencies: min-indent: 1.0.1 - dev: true /strip-json-comments/3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} dev: true + /strip-literal/1.0.1: + resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} + dependencies: + acorn: 8.8.2 + dev: true + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + /supports-color/7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} dependencies: has-flag: 4.0.0 + + /supports-color/8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 dev: true /supports-preserve-symlinks-flag/1.0.0: @@ -1769,6 +3842,15 @@ packages: is-promise: 4.0.0 dev: false + /svelte-fragment-component/1.2.0_svelte@3.55.0: + resolution: {integrity: sha512-rRstmz2oAy2Y/7X57tRaIAJdMYsa2K/MOx/YJN/ETb7Bj9U3vjgioz27dMG1hl2vAKFTtQpxDhC31ur7ECwpog==} + engines: {node: '>=10'} + peerDependencies: + svelte: 3.x + dependencies: + svelte: 3.55.0 + dev: false + /svelte-hmr/0.15.1_svelte@3.55.0: resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} engines: {node: ^12.20 || ^14.13.1 || >= 16} @@ -1778,10 +3860,45 @@ packages: svelte: 3.55.0 dev: true + /svelte-htm/1.2.0_svelte@3.55.0: + resolution: {integrity: sha512-6YFNncbyXbCa3PSoMQc/JR6O/Yg1OgNioH5rMgE88RVPzU8714y2Urfenlqs+ryRS+Inv+m6TJ9jH3W7wDCS1A==} + engines: {node: '>=10'} + peerDependencies: + svelte: 3.x + dependencies: + '@babel/runtime-corejs3': 7.20.13 + core-js: 3.28.0 + htm: 3.1.1 + svelte: 3.55.0 + svelte-fragment-component: 1.2.0_svelte@3.55.0 + svelte-hyperscript: 1.2.1_svelte@3.55.0 + dev: false + + /svelte-hyperscript/1.2.1_svelte@3.55.0: + resolution: {integrity: sha512-IVk91VDSOrhOUe0KI+Jfu7yCruUTOXgr4WysrK7hBNbFMDjeBOcPhk+LMYjUjdWxJx3+QSt3bUXj09YrUfRkjg==} + engines: {node: '>=10'} + peerDependencies: + svelte: 3.x + dependencies: + '@babel/runtime-corejs3': 7.20.13 + core-js: 3.28.0 + svelte: 3.55.0 + dev: false + /svelte-icons/2.1.0: resolution: {integrity: sha512-rHPQjweEc9fGSnvM0/4gA3pDHwyZyYsC5KhttCZRhSMJfLttJST5Uq0B16Czhw+HQ+HbSOk8kLigMlPs7gZtfg==} dev: false + /svelte-markdown/0.2.3_svelte@3.55.0: + resolution: {integrity: sha512-2h680NzTXnAD0CXhxe3GeHl6W+ayG4iKQRl+BIDRw+R0mUE0OiNxP1Vt8Rn+aWevB/LBiBIPCAwvL+0BkG057A==} + peerDependencies: + svelte: ^3.0.0 + dependencies: + '@types/marked': 4.0.8 + marked: 4.2.12 + svelte: 3.55.0 + dev: false + /svelte-multiselect/8.2.3: resolution: {integrity: sha512-cCnPFkG+0i2eBDaYUOgmQVa2TaJ6Xdjly0/tpch0XCfu4Rs0whbnEXP4QfKVloaAxEDUXwiIq/FHEYZ61xAklg==} dev: false @@ -1864,6 +3981,10 @@ packages: resolution: {integrity: sha512-xg9ckb8UeeIme4/5qlwCrl2QNmUZ8SCQYZn3Ji83cUsoASqRNy3KWjpmNmzYvPDqCHSZjruBBsoB7t5hwuzw5g==} dev: false + /svelte-websocket-store/1.1.33: + resolution: {integrity: sha512-E11veDt/iWHRmXAT+cKJdIPt5urM2c19HQpMQtzlNNpEP4U/OzspS+7tquKyxA9DZNtHL3d27tscQ5IDf5vAAg==} + dev: false + /svelte/3.55.0: resolution: {integrity: sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==} engines: {node: '>= 8'} @@ -1880,10 +4001,22 @@ packages: typescript: 4.9.4 dev: false + /symbol-tree/3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true + /text-table/0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /throttleit/1.0.0: + resolution: {integrity: sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==} + dev: true + + /through/2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + /tiny-glob/0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} dependencies: @@ -1891,18 +4024,63 @@ packages: globrex: 0.1.2 dev: true + /tinybench/2.3.1: + resolution: {integrity: sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==} + dev: true + + /tinypool/0.3.1: + resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy/1.1.1: + resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==} + engines: {node: '>=14.0.0'} + dev: true + + /tmp/0.2.1: + resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} + engines: {node: '>=8.17.0'} + dependencies: + rimraf: 3.0.2 + dev: true + /to-regex-range/5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - dev: true /totalist/3.0.0: resolution: {integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==} engines: {node: '>=6'} dev: true + /tough-cookie/2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + dependencies: + psl: 1.9.0 + punycode: 2.1.1 + dev: true + + /tough-cookie/4.1.2: + resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.1.1 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: true + + /tr46/3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + dependencies: + punycode: 2.1.1 + dev: true + /tslib/1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true @@ -1920,6 +4098,23 @@ packages: typescript: 4.9.4 dev: true + /tunnel-agent/0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /tweetnacl/0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + dev: true + + /type-check/0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + dev: true + /type-check/0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1927,16 +4122,30 @@ packages: prelude-ls: 1.2.1 dev: true + /type-detect/4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + /type-fest/0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} dev: true + /type-fest/0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + /typescript/4.9.4: resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} engines: {node: '>=4.2.0'} hasBin: true + /ufo/1.0.1: + resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==} + dev: true + /undici/5.14.0: resolution: {integrity: sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==} engines: {node: '>=12.18'} @@ -1944,12 +4153,106 @@ packages: busboy: 1.6.0 dev: true + /universalify/0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: true + + /universalify/2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + + /untildify/4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + dev: true + /uri-js/4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.1.1 dev: true + /url-parse/1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: true + + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: true + + /verror/1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + dev: true + + /vite-node/0.28.4_ovmyjmuuyckt3r3gpaexj2onji: + resolution: {integrity: sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==} + engines: {node: '>=v14.16.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.1.0 + pathe: 1.1.0 + picocolors: 1.0.0 + source-map: 0.6.1 + source-map-support: 0.5.21 + vite: 4.0.3_ovmyjmuuyckt3r3gpaexj2onji + transitivePeerDependencies: + - '@types/node' + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite/4.0.3_ovmyjmuuyckt3r3gpaexj2onji: + resolution: {integrity: sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.11.18 + esbuild: 0.16.11 + postcss: 8.4.20 + resolve: 1.22.1 + rollup: 3.8.1 + sass: 1.57.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /vite/4.0.3_sass@1.57.1: resolution: {integrity: sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -1995,6 +4298,124 @@ packages: vite: 4.0.3_sass@1.57.1 dev: true + /vitest/0.28.4_x3jjsgulbh7575qaqkjiilw4p4: + resolution: {integrity: sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==} + engines: {node: '>=v14.16.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/chai': 4.3.4 + '@types/chai-subset': 1.3.3 + '@types/node': 18.11.18 + '@vitest/browser': 0.28.4 + '@vitest/expect': 0.28.4 + '@vitest/runner': 0.28.4 + '@vitest/spy': 0.28.4 + '@vitest/ui': 0.28.4 + '@vitest/utils': 0.28.4 + acorn: 8.8.1 + acorn-walk: 8.2.0 + cac: 6.7.14 + chai: 4.3.7 + debug: 4.3.4 + jsdom: 21.1.0 + local-pkg: 0.4.3 + pathe: 1.1.0 + picocolors: 1.0.0 + source-map: 0.6.1 + std-env: 3.3.2 + strip-literal: 1.0.1 + tinybench: 2.3.1 + tinypool: 0.3.1 + tinyspy: 1.1.1 + vite: 4.0.3_ovmyjmuuyckt3r3gpaexj2onji + vite-node: 0.28.4_ovmyjmuuyckt3r3gpaexj2onji + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /w3c-xmlserializer/4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + dependencies: + xml-name-validator: 4.0.0 + dev: true + + /webidl-conversions/7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: true + + /whatwg-encoding/2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + dependencies: + iconv-lite: 0.6.3 + dev: true + + /whatwg-mimetype/3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + dev: true + + /whatwg-url/11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + dev: true + + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + /which-collection/1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + + /which-typed-array/1.1.9: + resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + is-typed-array: 1.1.10 + /which/2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2003,20 +4424,81 @@ packages: isexe: 2.0.0 dev: true + /why-is-node-running/2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} dev: true + /wrap-ansi/6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true + /ws/8.12.0: + resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /xml-name-validator/4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + dev: true + + /xmlchars/2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: true + /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yauzl/2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: true + /yocto-queue/0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /yocto-queue/1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true diff --git a/frontend/src/apis/auth.api.ts b/frontend/src/apis/auth.api.ts new file mode 100644 index 0000000..790f70f --- /dev/null +++ b/frontend/src/apis/auth.api.ts @@ -0,0 +1,15 @@ +import axios from 'axios'; +import { autoRefresh } from '../utils/utils'; +export const authInstance = axios.create({ + baseURL: `http://127.0.0.1:8002`, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Access-Control-Allow-Origin': '*', + //'X-CSRFToken': csrftoken != undefined ? csrftoken : '', + } +}); + +authInstance.interceptors.request.use(autoRefresh, (error) => { + Promise.reject(error); +}); diff --git a/frontend/src/apis/exo.api.ts b/frontend/src/apis/exo.api.ts index e13b439..cb51362 100644 --- a/frontend/src/apis/exo.api.ts +++ b/frontend/src/apis/exo.api.ts @@ -1,6 +1,7 @@ -import { browser } from '$app/environment'; import axios from 'axios'; import { parse, stringify } from 'qs' +import { autoRefresh } from '../utils/utils'; + export const exoInstance = axios.create({ paramsSerializer:{encode:(params)=> {return parse(params, {arrayFormat:"brackets"})}, serialize: (p)=>{return stringify(p, {arrayFormat: "repeat"})}}, baseURL: `http://127.0.0.1:8002`, @@ -9,7 +10,9 @@ export const exoInstance = axios.create({ Accept: 'application/json', 'Access-Control-Allow-Origin': '*', //'X-CSRFToken': csrftoken != undefined ? csrftoken : '', - ...(browser && - localStorage.getItem('token') && { Authorization: `Bearer ${localStorage.getItem('token')}` }) } }); + +exoInstance.interceptors.request.use(autoRefresh, (error) => { + Promise.reject(error); +}); diff --git a/frontend/src/apis/room.api.ts b/frontend/src/apis/room.api.ts new file mode 100644 index 0000000..0bced10 --- /dev/null +++ b/frontend/src/apis/room.api.ts @@ -0,0 +1,19 @@ +import axios from 'axios'; +import { autoRefresh } from '../utils/utils'; + +export const roomInstance = axios.create({ + baseURL: `http://127.0.0.1:8002/room`, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Access-Control-Allow-Origin': '*', + //'X-CSRFToken': csrftoken != undefined ? csrftoken : '', + } +}); + +roomInstance.interceptors.request.use( + autoRefresh, + (error) => { + Promise.reject(error); + } +); diff --git a/frontend/src/app.scss b/frontend/src/app.scss index 6b2d9d1..2ee2503 100644 --- a/frontend/src/app.scss +++ b/frontend/src/app.scss @@ -4,6 +4,44 @@ box-sizing: border-box; margin: 0; } +.spinner { + width: 30px; + height:30px; + border: 3px solid $contrast; + border-bottom-color: transparent; + border-radius: 50%; + animation: rotation 1s infinite linear; + + display: inline-block; + box-sizing: border-box; + +} + +.italic { + font-style: italic; +} +.underline { + text-decoration: underline; +} + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.container { + height: calc(100vh - 100px); // 100% - nav +} + + +*{ + scrollbar-width: auto!important; + scrollbar-color: $contrast transparent; +} .btn { border: none; @@ -13,9 +51,12 @@ transition: 0.3s; margin-bottom: 10px; margin-right: 7px; - padding: 0 10%; + padding: 0 50px; width: max-content; cursor: pointer; + &:disabled{ + cursor: not-allowed + } } .primary-btn { @@ -61,7 +102,7 @@ margin: 0; &:focus { outline: none; - border-bottom-color: red; + border-bottom-color: $contrast; } } @@ -79,4 +120,9 @@ .sv-dropdown{ z-index: 10!important; -} \ No newline at end of file +} + +.strong{ + font-weight: 900; +} + diff --git a/frontend/src/components/NavBar.svelte b/frontend/src/components/NavBar.svelte new file mode 100644 index 0000000..af1aac1 --- /dev/null +++ b/frontend/src/components/NavBar.svelte @@ -0,0 +1,207 @@ + + + + \ No newline at end of file diff --git a/frontend/src/components/NavLink.svelte b/frontend/src/components/NavLink.svelte index 1c92d6a..2d9c0ad 100644 --- a/frontend/src/components/NavLink.svelte +++ b/frontend/src/components/NavLink.svelte @@ -2,15 +2,16 @@ import { page } from '$app/stores'; export let href = "/"; export let exact = false; + export let no_hover = false; - + diff --git a/frontend/src/components/auth/PasswordForm.svelte b/frontend/src/components/auth/PasswordForm.svelte new file mode 100644 index 0000000..64f7cc8 --- /dev/null +++ b/frontend/src/components/auth/PasswordForm.svelte @@ -0,0 +1,31 @@ + + +{#if !!$myForm} +
+ + +
+{/if} + \ No newline at end of file diff --git a/frontend/src/components/auth/RoomList.svelte b/frontend/src/components/auth/RoomList.svelte new file mode 100644 index 0000000..6372d5a --- /dev/null +++ b/frontend/src/components/auth/RoomList.svelte @@ -0,0 +1,40 @@ + + +
+ +
+ + \ No newline at end of file diff --git a/frontend/src/components/auth/Section.svelte b/frontend/src/components/auth/Section.svelte new file mode 100644 index 0000000..cd4ea28 --- /dev/null +++ b/frontend/src/components/auth/Section.svelte @@ -0,0 +1,53 @@ + + +
+

+
+
+ {title}

+
+ +
+ {#if !!onValidate} +
+ +
+ + {/if} +
+ + \ No newline at end of file diff --git a/frontend/src/components/auth/UserConfirm.svelte b/frontend/src/components/auth/UserConfirm.svelte new file mode 100644 index 0000000..224673d --- /dev/null +++ b/frontend/src/components/auth/UserConfirm.svelte @@ -0,0 +1,35 @@ + + +
+

Veuillez confirmer votre identité

+ +
+ + +
+
+ \ No newline at end of file diff --git a/frontend/src/components/exos/Card.svelte b/frontend/src/components/exos/Card.svelte index c2b3281..2c507eb 100644 --- a/frontend/src/components/exos/Card.svelte +++ b/frontend/src/components/exos/Card.svelte @@ -7,11 +7,13 @@ import TagContainer from './TagContainer.svelte'; import PrivacyIndicator from './PrivacyIndicator.svelte'; import MdContentCopy from 'svelte-icons/md/MdContentCopy.svelte'; + import type { Writable } from 'svelte/store'; + export let exo: Exercice; const { show } = getContext<{ show: Function }>('modal'); const { navigate } = getContext<{ navigate: Function }>('navigation'); - const { isAuth } = getContext<{ isAuth: boolean }>('auth'); + const { isAuth } = getContext<{ isAuth: Writable }>('auth'); const exerciceStore = getContext('exos'); const tagsStore = getContext('tags'); @@ -19,10 +21,6 @@ const handleClick = () => { opened = true; navigate(`/exercices/${exo.id_code}`); - }; - - let tg = false; - $: !!opened && show( ModalCard, { @@ -35,36 +33,36 @@ opened = false; } ); + }; -
{}} - on:keypress={() => {}} -> +
{}} on:keypress={() => {}}>

{exo.name}

-

Exemples

- {#if !!exo.consigne}

{exo.consigne}

{/if} - {#each exo.examples.data.slice(0, 3) as ex} -

{ex.calcul}

- {/each} + {#if exo.examples != null} +

Exemples

+ {#if !!exo.consigne}

{exo.consigne}

{/if} + {#each exo.examples.data.slice(0, 3) as ex} +

{ex.calcul}

+ {/each} + {:else} +

Aucun exemple disponible

+ {/if}
- {#if !!isAuth} - {#if exo.is_author && exo.original == null} -
- - {exo.private == true ? 'Privé' : 'Public'} -
- {:else if !exo.is_author} -
- - Par {exo.author.username} - + {#if !!$isAuth && exo.is_author && exo.original == null } +
+ + {exo.private == true ? 'Privé' : 'Public'} +
+ {:else if !exo.is_author} +
+ + Par {exo.author.username} + + {#if !!$isAuth}
{}} on:click|stopPropagation={() => { @@ -78,15 +76,15 @@ >
-
- {:else if exo.is_author && exo.original != null} -
- Par {exo.original?.author} -
- {/if}{/if} + {/if} +
+ {:else if exo.is_author && exo.original != null} +
+ Par {exo.original?.author} +
+ {/if}
- {#if !!isAuth} + {#if !!$isAuth} {/if} @@ -188,7 +186,7 @@ background-color: $background; min-height: 250px; max-height: 300px; - &:not(.tagMode):hover { + &:hover { transform: translateX(10px) translateY(-10px); } } diff --git a/frontend/src/components/exos/CreateCard.svelte b/frontend/src/components/exos/CreateCard.svelte index 057e7e1..3ae680f 100644 --- a/frontend/src/components/exos/CreateCard.svelte +++ b/frontend/src/components/exos/CreateCard.svelte @@ -17,9 +17,10 @@
- diff --git a/frontend/src/components/exos/ExoList.svelte b/frontend/src/components/exos/ExoList.svelte new file mode 100644 index 0000000..f64a820 --- /dev/null +++ b/frontend/src/components/exos/ExoList.svelte @@ -0,0 +1,165 @@ + + +
{ + if ( + more != undefined && + $exos.hasMore && + more.offsetHeight + more.scrollTop >= more.scrollHeight + ) { + fetchNextPage(); + } + }} +> +
+ { + if (updateText != null) { + clearTimeout(updateText); + } + + updateText = window.setTimeout(() => { + search = e.currentTarget.value; + }, 500); + }} + /> + {#if !!$isAuth} +
+ + +
{/if} +
+
+ {#each $exos.data as e (e.id_code)} +
{ + if ($selectedExos.map((s) => s.exercice_id).includes(e.id_code)) { + selectedExos.update((n) => { + return [...n.filter((s) => s.exercice_id != e.id_code)]; + }); + } else { + selectedExos.update((n) => { + return [...n, { quantity: 10, exercice_id: e.id_code, name: e.name }]; + }); + } + }} + class:selected={$selectedExos.map((s) => s.exercice_id).includes(e.id_code)} + on:keydown={() => {}} + > +

{e.name}

+ +
+ {/each} + {#if $exos.hasMore} +

+ {/if} +
+
+ + diff --git a/frontend/src/components/exos/Feed.svelte b/frontend/src/components/exos/Feed.svelte index 77064fa..c4ef081 100644 --- a/frontend/src/components/exos/Feed.svelte +++ b/frontend/src/components/exos/Feed.svelte @@ -3,96 +3,140 @@ import Card from './Card.svelte'; import Head from './Head.svelte'; import ModalCard from './ModalCard.svelte'; - import { Query, useQueryClient, type QueryOptions } from '@sveltestack/svelte-query'; import { getExo, getExos, getTags } from '../../requests/exo.request'; import { page } from '$app/stores'; import { onMount } from 'svelte'; import Pagination from './Pagination.svelte'; import { goto } from '$app/navigation'; - import { writable } from 'svelte/store'; + import {type Writable, writable} from 'svelte/store'; import { setContext } from 'svelte'; import type { Page, Tag } from '../../types/exo.type'; import type { Store } from '../../types/api.type'; + import { page as p } from '$app/stores'; const { show } = getContext<{ show: Function }>('modal'); - const { navigate } = getContext<{ navigate: Function }>('navigation'); + const { navigate, insertUrl } = getContext<{ navigate: Function; insertUrl: Function }>( + 'navigation' + ); + const { isAuth } = getContext<{ isAuth: Writable }>('auth'); + let filter = $isAuth ? 'user' : 'public'; - let filter = 'user'; - - const exerciceStore = writable>({ + const exerciceStore = writable>({ isLoading: false, isFetching: false, isSuccess: false, data: undefined }); - const tagStore = writable>({ + const tagStore = writable>({ isLoading: false, isFetching: false, isSuccess: false, - data: undefined + data: [] }); setContext('exos', exerciceStore); setContext('tags', tagStore); + onMount(() => { + let page = $page.url.searchParams.get('page') + if(page == null){ + page = '1' + } if ($page.params.slug != undefined && !['user', 'public'].includes($page.params.slug)) { getExo($page.params.slug).then((r) => { - show(ModalCard, { exo: r, exos: exerciceStore, tags: tagStore }, () => navigate('/exercices/' + filter)); + insertUrl('exercices/' + filter); + show(ModalCard, { exo: r, exos: exerciceStore, tags: tagStore }, () => navigate(-1)); }); - } else if ($page.params.slug == undefined) { - goto('/exercices/public'); + } else if ($page.params.slug == undefined || $page.params.slug == "user") { + filter = $isAuth ? 'user' : 'public'; + goto(`/exercices/${filter}?${new URLSearchParams({page}).toString()}`) + } else if($page.params.slug == "public"){ + filter = 'public'; + goto(`/exercices/${filter}?${new URLSearchParams({page}).toString()}`) } }); $: filter = ['user', 'public'].includes($page.params.slug) ? $page.params.slug : filter; + /*$: if(['user', 'public'].includes($page.params.slug) && !$isAuth){ + filter = "public" + //goto('/exercices/' + filter) + }*/ + const size = 15; + $: activePage = parseInt($page.url.searchParams.get('page')!) || 1; + let search = ''; + let selected: Tag[] = []; + $: { + if(!$isAuth){ + filter = 'public' + } exerciceStore.update((s) => { return { ...s, isFetching: true }; }); getExos(filter as 'public' | 'user', { - page: activePage, + page: activePage == 0 ? 1: activePage, search, + size, tags: [...selected.map((t) => t.id_code)] - }).then((r) => { - exerciceStore.update((e) => { - return { ...e, isSuccess: true, isFetching: false, data: r }; - }); - }); + }) + .then((r) => { + console.log('R', r); + if (activePage > r.totalPage && r.total != 0 && r.totalPage != 0) { + activePage = r.totalPage; + //$p.url.searchParams.set('page', String(activePage)); + goto(`?${new URLSearchParams({page: activePage}).toString()}`); + return; + } + exerciceStore.update((e) => { + return { ...e, isSuccess: true, isFetching: false, data: r }; + }); + }) + .catch(console.log); } $: { - tagStore.update((s)=>{return {...s, isFetching: true}}); - getTags().then(r=>{ - tagStore.update((e) => { - return { ...e, isSuccess: true, isFetching: false, data: r }; + if($isAuth) { + tagStore.update((s) => { + return {...s, isFetching: true}; }); - }) + getTags().then((r) => { + tagStore.update((e) => { + return {...e, isSuccess: true, isFetching: false, data: r}; + }); + }); + } } - let activePage = parseInt($page.url.searchParams.get('page')!) || 1; - let search = ''; - let selected: Tag[] = []; + + + - -{#if $tagStore.isSuccess == true && $tagStore.data != undefined} +{#if $tagStore.data != undefined} {/if} {#if $tagStore.isFetching == true} Fetching -{/if} +{/if}
-

- Tous les exercices -

-

- Vous retrouverez ici tous les exercices que vous avez créé ou copié depuis les exercices - publics -

+ {#if filter == 'user'} +

+ Vos exercices +

+

+ Vous retrouverez ici tous les exercices que vous avez créé ou copié depuis les exercices + publics +

+ {:else} +

+ Tous les exercices +

+

Vous retrouverez ici tous les exercices créés par les autres utilisateurs

+ {/if}
{#if $exerciceStore.data != undefined} {#each $exerciceStore.data.items.filter((e) => e != null && selected.every((t) => e.tags @@ -100,11 +144,63 @@ .includes(t.id_code))) as e} {/each} - + {:else} + {#each Array(10) as i} +
+ {/each} {/if}
+{#if $exerciceStore.data != undefined} + +{/if} diff --git a/frontend/src/components/exos/Head.svelte b/frontend/src/components/exos/Head.svelte index 0f3fe91..fd5a545 100644 --- a/frontend/src/components/exos/Head.svelte +++ b/frontend/src/components/exos/Head.svelte @@ -7,20 +7,31 @@ import type { Writable } from 'svelte/store'; import type { Store } from '../../types/api.type'; - const { navigate } = getContext<{navigate: Function}>('navigation'); - const { show, close } = getContext<{show: Function, close: Function}>('modal'); + const { navigate } = getContext<{ navigate: Function }>('navigation'); + const { show, close } = getContext<{ show: Function; close: Function }>('modal'); + export let location = 'public'; export let search = ''; export let selected: Tag[] = []; - const { isAuth } = getContext<{ isAuth: boolean }>('auth'); + const { isAuth } = getContext<{ isAuth: Writable }>('auth'); - const tags: Writable> = getContext('tags') + const tags: Writable> = getContext('tags'); const exerciceStore: Writable> = getContext('exos'); + let updateText = null; + let tmp ="" + $: { + if(updateText != null){ + clearTimeout(updateText); + } + updateText = window.setTimeout(() => { + search = tmp; + }, 500); + }
- {#if !!isAuth} + {#if !!$isAuth}