diff --git a/backend/api/database/exercices/crud.py b/backend/api/database/exercices/crud.py index 9d807ba..ff272fa 100644 --- a/backend/api/database/exercices/crud.py +++ b/backend/api/database/exercices/crud.py @@ -2,7 +2,7 @@ from fastapi import Query, HTTPException, status import os import shutil from typing import IO, List -from sqlmodel import Session, select, or_ +from sqlmodel import Session, select, or_, col from generateur.generateur_main import generate_from_data, generate_from_path from database.auth.models import User from database.db import get_session @@ -62,22 +62,23 @@ def clone_exo_db(exercice: Exercice, user: User, db: Session): db.refresh(new_exo) return new_exo -def update_exo_db(old_exo: Exercice, new_exo: ExerciceEdit,supports: Supports, exo_source: IO | None, db: Session): +def update_exo_db(old_exo: Exercice, new_exo: ExerciceEdit, supports: Supports | None, exo_source: IO | None, db: Session): exo_data = new_exo.dict(exclude_unset=True, exclude_none=True) for key, value in exo_data.items(): setattr(old_exo, key, value) - old_exo.csv = supports['csv'] - old_exo.pdf = supports['pdf'] - old_exo.web = supports['web'] + if supports is not None: + old_exo.csv = supports['csv'] + old_exo.pdf = supports['pdf'] + old_exo.web = supports['web'] - example = { - "type": ExampleEnum.csv if supports['csv'] == True else ExampleEnum.web if supports['web'] == True else None, - "data": generate_from_data(exo_source.read(), 3, "csv" if supports['csv'] == True else "web" if supports['web'] == True else None, True) if supports['csv'] == True == True or supports['web'] == True == True else None - } - old_exo.examples = example + example = { + "type": ExampleEnum.csv if supports['csv'] == True else ExampleEnum.web if supports['web'] == True else None, + "data": generate_from_data(exo_source.read(), 3, "csv" if supports['csv'] == True else "web" if supports['web'] == True else None, True) if supports['csv'] == True == True or supports['web'] == True == True else None + } + old_exo.examples = example if exo_source: os.remove(add_fast_api_root(old_exo.exo_source)) @@ -87,7 +88,6 @@ def update_exo_db(old_exo: Exercice, new_exo: ExerciceEdit,supports: Supports, e db.refresh(old_exo) return old_exo - def delete_exo_db(exo: Exercice, db: Session): db.delete(exo) db.commit() @@ -98,24 +98,27 @@ def get_or_create_tag(tag: TagCreate, user: User, db: Session): tag_db = db.exec(select(Tag).where(Tag.author_id == user.id).where(or_( Tag.id_code == tag.id_code, Tag.label == tag.label))).first() if tag_db is not None: - return tag_db + return tag_db, False id_code = generate_unique_code(Tag, db) tag_db = Tag(**{**tag.dict(exclude_unset=True), 'id_code': id_code, 'author_id': user.id}) db.add(tag_db) db.commit() db.refresh(tag_db) - return tag_db + return tag_db, True def add_tags_db(exo: Exercice, tags: List[TagCreate], user: User, db: Session): + new = [] for tag in tags: - tag_db = get_or_create_tag(tag, user, db) + tag_db, created = get_or_create_tag(tag, user, db) + if created: + new.append(tag_db) exo.tags.append(tag_db) db.add(exo) db.commit() db.refresh(exo) - return exo + return exo, new def remove_tag_db(exo: Exercice, tag: Tag, db: Session): @@ -171,13 +174,11 @@ def check_tag_author(tag_id: str, user: User = Depends(get_current_user), db: Se def get_tags_dependency(tags: List[str] | None = Query(None), db: Session = Depends(get_session)): if tags is None: - return None - validated_tags = [] - for t in tags: - tag = db.exec(select(Tag.id).where(Tag.id_code == t)).first() - if tag is not None: - validated_tags.append(tag) - return validated_tags + return [] + validated_tags = db.exec( + select(Tag.id_code, Tag.id).where(col(Tag.id_code).in_(tags))).all() + + return [t.id for t in validated_tags] #Serialize @@ -185,9 +186,17 @@ def get_tags_dependency(tags: List[str] | None = Query(None), db: Session = Depe def check_author(exo: Exercice, user_id:int): return exo.author_id == user_id -def serialize_exo(*, exo: ExerciceRead, user_id: User = None, db: Session): +def serialize_exo(*, exo: Exercice, user_id: User = None, db: Session): tags = parse_exo_tags(exo_id=exo.id, user_id=user_id, db=db) if user_id is not None else [] is_author = user_id is not None and check_author(exo=exo, user_id=user_id) - return ExerciceReadFull(**exo.dict(), author=exo.author, original=exo.original, tags=tags, is_author=is_author, supports={**exo.dict()}) + print('USER', exo.dict(), exo) + if exo.original is not None: + print('TEST', db.exec(select(User).where(User.id == exo.original.author_id)).all()) + author = db.exec(select(User).where( + User.id == exo.original.author_id)).all()[0] + original = {**exo.original.dict(), 'author': author.username} + else: + original = None + return ExerciceReadFull(**{**exo.dict(), "author":exo.author, "original":original, "tags":tags, "is_author":is_author, "supports":{**exo.dict()}}) diff --git a/backend/api/database/exercices/models.py b/backend/api/database/exercices/models.py index fd78001..388c2ce 100644 --- a/backend/api/database/exercices/models.py +++ b/backend/api/database/exercices/models.py @@ -125,11 +125,6 @@ class ExerciceEdit(ExerciceCreate): pass -class ExerciceOrigin(SQLModel): - #id: int - id_code: str - name: str - @@ -138,6 +133,13 @@ class Author(SQLModel): username: str +class ExerciceOrigin(SQLModel): + #id: int + id_code: str + name: str + author: str + + @@ -164,12 +166,15 @@ class ExerciceReadBase(ExerciceBase): return get_filename_from_path(value) return value -class ExerciceRead(ExerciceBase): +class ExerciceRead(ExerciceBase, Supports): + id_code: str id: int author_id:int + exo_source: str author: User original: Optional[Exercice] tags: List[Tag] + examples: Example class ExerciceReadFull(ExerciceReadBase): supports: Supports diff --git a/backend/api/exo.py b/backend/api/exo.py new file mode 100644 index 0000000..ec2f602 --- /dev/null +++ b/backend/api/exo.py @@ -0,0 +1,12 @@ + +import requests +def exos(nb, username, password): + rr = requests.post('http://localhost:8002/register', data={"username": username, + 'password': password, 'password_confirm': password}) + token = rr.json()['access_token'] + for i in range(nb): + r = requests.post('http://localhost:8002/exercices', data={"name": "FakingTest" + str(i), "consigne": "consigne", "private": False}, files={ + 'file': ('test.py', open('./tests/testing_exo_source/exo_source.py', 'rb'))}, headers={"Authorization": "Bearer " + token}) + print('DONE') + +exos(100, "lilianTest", "Pomme937342") \ No newline at end of file diff --git a/backend/api/main.py b/backend/api/main.py index b804a64..53ff472 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -37,6 +37,7 @@ origins = [ "https://localhost:8001", "http://localhost", "http://localhost:8080", + "http://localhost:5173" ] app.add_middleware( @@ -45,6 +46,7 @@ app.add_middleware( allow_credentials=True, allow_methods=["*"], allow_headers=["*"], + expose_headers=['*'] ) admin = Admin(app, engine) diff --git a/backend/api/routes/exercices/routes.py b/backend/api/routes/exercices/routes.py index caf75c2..5ece63b 100644 --- a/backend/api/routes/exercices/routes.py +++ b/backend/api/routes/exercices/routes.py @@ -1,3 +1,4 @@ +from pydantic import BaseModel from enum import Enum from typing import List from fastapi import APIRouter, Depends, Path, Query, UploadFile, HTTPException, status @@ -11,8 +12,10 @@ from services.exoValidation import validate_file, validate_file_optionnal from services.io import add_fast_api_root, get_filename_from_path from fastapi.responses import FileResponse from sqlmodel import func -from fastapi_pagination import Page, paginate +from fastapi_pagination import paginate ,Page +from services.models import Page from fastapi_pagination.ext.sqlalchemy_future import paginate as p + router = APIRouter(tags=['exercices']) class ExoType(str, Enum): @@ -27,7 +30,7 @@ def filter_exo_by_tags(exos: List[tuple[Exercice, str]], tags: List[Tag]): return valid_exos -def queryFilters_dependency(search: str = "", tags: List[int] | None = Depends(get_tags_dependency), type: ExoType | None = Query(default = None)): +def queryFilters_dependency(search: str = "", tags: List[str] | None = Depends(get_tags_dependency), type: ExoType | None = Query(default = None)): return search, tags, type @@ -53,15 +56,10 @@ def clone_exo(exercice: Exercice | None = Depends(check_private), user: User = D @router.get('/exercices/user', response_model=Page[ExerciceRead|ExerciceReadFull]) -def get_user_exercices(user: User = Depends(get_current_user), queryFilters: tuple[str, List[int] | None] = Depends(queryFilters_dependency), db: Session = Depends(get_session)): +def get_user_exercices(user: User = Depends(get_current_user), queryFilters: tuple[str, List[int] | None, ExoType | None] = Depends(queryFilters_dependency), db: Session = Depends(get_session)): search, tags, type = queryFilters - if tags is not None and len(tags) != 0: - statement = select(Exercice, func.group_concat( - ExercicesTagLink.tag_id)) - else: - statement = select(Exercice) - + statement = select(Exercice) statement = statement.where(Exercice.author_id == user.id) statement = statement.where(Exercice.name.startswith(search)) @@ -72,18 +70,14 @@ def get_user_exercices(user: User = Depends(get_current_user), queryFilters: tup if type == ExoType.web: statement = statement.where(Exercice.web == True) - if tags is not None and len(tags) != 0: - statement = statement.join(ExercicesTagLink).where( - col(ExercicesTagLink.tag_id).in_(tags)).group_by(ExercicesTagLink.exercice_id) - - #exercices = db.exec(statement).all() + for t in tags: + sub = select(ExercicesTagLink).where(ExercicesTagLink.exercice_id==Exercice.id).where( + ExercicesTagLink.tag_id == t).exists() + statement = statement.where(sub) page = p(db, statement) exercices = page.items - if tags is not None and len(tags) != 0: - exercices = filter_exo_by_tags(exercices, tags) page.items = [ serialize_exo(exo=e, user_id=user.id, db=db) for e in exercices] - return page @@ -92,12 +86,7 @@ def get_public_exercices(user: User | None = Depends(get_current_user_optional), search, tags, type = queryFilters if user is not None: - if tags is not None and len(tags) != 0: - statement = select(Exercice, func.group_concat( - ExercicesTagLink.tag_id)) - else: - statement = select(Exercice) - + statement = select(Exercice) statement = statement.where(Exercice.author_id != user.id) statement = statement.where(Exercice.private == False) statement = statement.where(Exercice.name.startswith(search)) @@ -108,19 +97,19 @@ def get_public_exercices(user: User | None = Depends(get_current_user_optional), statement = statement.where(Exercice.pdf == True) if type == ExoType.web: statement = statement.where(Exercice.web == True) + + for t in tags: + sub = select(ExercicesTagLink).where(ExercicesTagLink.exercice_id==Exercice.id).where( + ExercicesTagLink.tag_id == t).exists() + statement = statement.where(sub) - if tags is not None and len(tags) != 0: - statement = statement.join(ExercicesTagLink).where( - col(ExercicesTagLink.tag_id).in_(tags)).group_by(ExercicesTagLink.exercice_id) - - exercices = db.exec(statement).all() - - if tags is not None and len(tags) != 0: - exercices = filter_exo_by_tags(exercices, tags) - - exercices = [ + page = p(db, statement) + print('¨PAGE', page) + exercices = page.items + page.items = [ serialize_exo(exo=e, user_id=user.id, db=db) for e in exercices] - return exercices + return page + else: statement = select(Exercice) statement = statement.where(Exercice.private == False) @@ -131,8 +120,12 @@ def get_public_exercices(user: User | None = Depends(get_current_user_optional), statement = statement.where(Exercice.pdf == True) if type == ExoType.web: statement = statement.where(Exercice.web == True) - exercices = db.exec(statement).all() - return paginate([serialize_exo(exo=e, user_id=None, db=db) for e in exercices]) + + page = p(db, statement) + exercices = page.items + page.items = [ + serialize_exo(exo=e, user_id=None, db=db) for e in exercices] + return page @router.get('/exercice/{id_code}', response_model=ExerciceReadFull) @@ -153,7 +146,7 @@ def update_exo(file: UploadFile = Depends(validate_file_optionnal), exo: Exercic if file: file_obj = file["file"].file._file file_obj.name = file['file'].filename - exo_obj = update_exo_db(exo, exercice,file['supports'] if file is not None else None, file_obj, db) + exo_obj = update_exo_db(exo, exercice, file['supports'] if file is not None else None, file_obj, db) return serialize_exo(exo=exo_obj, user_id=exo_obj.author_id, db=db) @@ -169,13 +162,19 @@ def delete_exercice(exercice: Exercice | bool | None = Depends(check_exercice_au return {'detail': 'Exercice supprimé avec succès'} -@router.post('/exercice/{id_code}/tags', response_model=ExerciceReadFull, tags=['tags']) + +class NewTags(BaseModel): + exo: ExerciceReadFull + tags: list[TagRead] + + +@router.post('/exercice/{id_code}/tags', response_model=NewTags, tags=['tags']) def add_tags(tags: List[TagCreate], exo: Exercice | None = Depends(get_exo_dependency), db: Session = Depends(get_session), user: User = Depends(get_current_user)): if exo is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail='Exercice introuvable') - exo_obj = add_tags_db(exo, tags, user, db) - return serialize_exo(exo=exo_obj, user_id=user.id, db=db) + exo_obj, new = add_tags_db(exo, tags, user, db) + return {"exo":serialize_exo(exo=exo_obj, user_id=user.id, db=db), "tags": new} @router.delete('/exercice/{id_code}/tags/{tag_id}', response_model=ExerciceReadFull, tags=['tags']) diff --git a/backend/api/services/models.py b/backend/api/services/models.py new file mode 100644 index 0000000..249c2a9 --- /dev/null +++ b/backend/api/services/models.py @@ -0,0 +1,42 @@ +from __future__ import annotations +from typing import TypeVar, Generic, Sequence + +from fastapi_pagination import Params +from fastapi_pagination.bases import AbstractPage, AbstractParams, BasePage +from math import ceil +from pydantic import conint + +T = TypeVar("T") + +class Page(BasePage[T], Generic[T]): + page: conint(ge=1) # type: ignore + size: conint(ge=1) + totalPage: conint(ge=0) + hasMore: bool + + + __params_type__ = Params # Set params related to Page + + @classmethod + def create( + cls, + items: Sequence[T], + total: int, + params: AbstractParams, + ) -> Page[T]: + print("PARAMS", params) + totalPage = ceil(total/params.size) + return cls( + total=total, + items=items, + page=params.page, + size=params.size, + totalPage = totalPage, + hasMore= params.page < totalPage + ) + return { + "items": items, + "total": total, + "size": params, + } + return cls(results=items) diff --git a/backend/api/tests/test_exos.py b/backend/api/tests/test_exos.py index 3c1286b..4536983 100644 --- a/backend/api/tests/test_exos.py +++ b/backend/api/tests/test_exos.py @@ -6,7 +6,6 @@ from tests.test_auth import test_register def test_create(client: TestClient, name="test_exo", consigne="consigne", private=False, user=None): - if user == None: token = test_register(client, username="lilian")['access'] username = 'lilian' @@ -145,6 +144,18 @@ def test_update(client: TestClient): '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} return r.json() +def test_update_no_file(client: TestClient): + token = test_register(client, username="lilian")['access'] + id_code = test_create(client, user={'token': token, 'username': "lilian"}, + consigne="testconsigne")['id_code'] + r = client.put('/exercice/' + id_code, data={"name": "name", "private": True}, headers={"Authorization": "Bearer " + token}) + print(r.json()) + assert r.status_code == 200 + assert 'id_code' in r.json() + assert r.json() == {'name': "name", 'consigne': "testconsigne", 'private': True, 'id_code': id_code, 'author': {'username': 'lilian'}, 'original': None, '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} + return r.json() + def test_update_missing_name(client: TestClient): token = test_register(client, username="lilian")['access'] @@ -346,19 +357,30 @@ def test_get_users_exos_page(client: TestClient): token1 = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] - prv = test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv2 = test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv3= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv4= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv5= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv6= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv7= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv8= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv9= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv10= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv11 = test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv12 = test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - + prv = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv2 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv3 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv4 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv5 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv6 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv7 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv8 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv9 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv10 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv11 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv12 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) r = client.get('/exercices/user', headers={'Authorization': 'Bearer ' + token1}, params={"page": 2, "size": 10}) @@ -366,24 +388,36 @@ def test_get_users_exos_page(client: TestClient): assert r.json()['page'] == 2 assert r.json()['size'] == 10 assert r.json()["items"] == [prv11, prv12] - + + def test_get_users_exos_page_up(client: TestClient): token1 = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] - prv = test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv2 = test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv3= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv4= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv5= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv6= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv7= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv8= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv9= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv10= test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv11 = test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - prv12 = test_create(client, private=True, user={'token': token1, 'username': "lilian"}) - + prv = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv2 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv3 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv4 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv5 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv6 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv7 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv8 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv9 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv10 = test_create(client, private=True, user={ + 'token': token1, 'username': "lilian"}) + prv11 = test_create(client, private=True, user={ + 'token': token2, 'username': "lilian2"}) + prv12 = test_create(client, private=True, user={ + 'token': token2, 'username': "lilian2"}) r = client.get('/exercices/user', headers={'Authorization': 'Bearer ' + token1}, params={"page": 2, "size": 10}) @@ -393,6 +427,7 @@ def test_get_users_exos_page_up(client: TestClient): assert r.json()["items"] == [] + def test_get_user_with_search(client: TestClient): token1 = test_register(client, username="lilian")['access'] token2 = test_register(client, username="lilian2")['access'] @@ -553,7 +588,7 @@ def test_get_public_no_auth(client: TestClient): r = client.get('/exercices/public') print(r.json()) assert r.json()['items'] == [{**public1, 'is_author': False}, - {**public2, 'is_author': False}] + {**public2, 'is_author': False}] def test_get_exo_no_auth(client: TestClient): @@ -561,7 +596,7 @@ def test_get_exo_no_auth(client: TestClient): exo = test_add_tags(client, user={'token': token, 'username': "lilian"}) r = client.get('/exercice/' + exo['id_code']) - assert r.json()['items'] == {**exo, "tags": [], 'is_author': False} + assert r.json() == {**exo, "tags": [], 'is_author': False} def test_get_exo_no_auth_private(client: TestClient): @@ -630,7 +665,7 @@ def test_get_csv(client: TestClient): r = client.get('/exercices/public', params={"type": "csv"}) - assert r.json() == [{**exoCsv.json(), 'is_author': False}] + assert r.json()['items'] == [{**exoCsv.json(), 'is_author': False}] def test_get_pdf(client: TestClient): @@ -644,7 +679,7 @@ def test_get_pdf(client: TestClient): r = client.get('/exercices/public', params={"type": "pdf"}) - assert r.json() == [{**exoPdf.json(), 'is_author': False}] + assert r.json()['items'] == [{**exoPdf.json(), 'is_author': False}] def test_get_web(client: TestClient): @@ -657,12 +692,13 @@ def test_get_web(client: TestClient): 'file': ('test.py', open('tests/testing_exo_source/exo_source_web_only.py', 'rb'))}, headers={"Authorization": "Bearer " + token}) r = client.get('/exercices/public', params={"type": "web"}) - + assert r.json() == [{**exoWeb.json(), 'is_author': False}] def test_get_invalid_type(client: TestClient): - + r = client.get('/exercices/public', params={"type": "lol"}) - - assert r.json() == {"detail": {'type_error': "value is not a valid enumeration member; permitted: 'csv', 'pdf', 'web'"}} + + assert r.json() == {"detail": { + 'type_error': "value is not a valid enumeration member; permitted: 'csv', 'pdf', 'web'"}} diff --git a/frontend/package.json b/frontend/package.json index 8c4645d..e90595c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "devDependencies": { "@sveltejs/adapter-auto": "^1.0.0", "@sveltejs/kit": "^1.0.0", + "@types/chroma-js": "^2.1.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.28.0", @@ -21,13 +22,27 @@ "eslint-plugin-svelte3": "^4.0.0", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.8.1", + "sass": "^1.53.0", "svelte": "^3.54.0", "svelte-check": "^2.9.2", + "svelte-preprocess": "^4.10.7", "tslib": "^2.4.1", "typescript": "^4.9.3", - "vite": "^4.0.0", - "sass": "^1.53.0", - "svelte-preprocess": "^4.10.7" + "vite": "^4.0.0" }, - "type": "module" + "type": "module", + "dependencies": { + "@sveltestack/svelte-query": "^1.6.0", + "@types/qs": "^6.9.7", + "axios": "^1.2.2", + "chroma-js": "^2.4.2", + "jwt-decode": "^3.1.2", + "qs": "^6.11.0", + "svelecte": "^3.13.0", + "svelte-forms": "^2.3.1", + "svelte-icons": "^2.1.0", + "svelte-multiselect": "^8.2.3", + "svelte-navigator": "^3.2.2", + "svelte-routing": "^1.6.0" + } } \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index f2c6e94..5fb01a1 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -3,24 +3,52 @@ lockfileVersion: 5.4 specifiers: '@sveltejs/adapter-auto': ^1.0.0 '@sveltejs/kit': ^1.0.0 + '@sveltestack/svelte-query': ^1.6.0 + '@types/chroma-js': ^2.1.4 + '@types/qs': ^6.9.7 '@typescript-eslint/eslint-plugin': ^5.45.0 '@typescript-eslint/parser': ^5.45.0 + axios: ^1.2.2 + chroma-js: ^2.4.2 eslint: ^8.28.0 eslint-config-prettier: ^8.5.0 eslint-plugin-svelte3: ^4.0.0 + jwt-decode: ^3.1.2 prettier: ^2.8.0 prettier-plugin-svelte: ^2.8.1 + qs: ^6.11.0 sass: ^1.53.0 + svelecte: ^3.13.0 svelte: ^3.54.0 svelte-check: ^2.9.2 + svelte-forms: ^2.3.1 + svelte-icons: ^2.1.0 + svelte-multiselect: ^8.2.3 + svelte-navigator: ^3.2.2 svelte-preprocess: ^4.10.7 + svelte-routing: ^1.6.0 tslib: ^2.4.1 typescript: ^4.9.3 vite: ^4.0.0 +dependencies: + '@sveltestack/svelte-query': 1.6.0 + '@types/qs': 6.9.7 + axios: 1.2.2 + chroma-js: 2.4.2 + jwt-decode: 3.1.2 + qs: 6.11.0 + svelecte: 3.13.0 + svelte-forms: 2.3.1 + svelte-icons: 2.1.0 + svelte-multiselect: 8.2.3 + svelte-navigator: 3.2.2_niwyv7xychq2ag6arq5eqxbomm + svelte-routing: 1.6.0_niwyv7xychq2ag6arq5eqxbomm + 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 + '@types/chroma-js': 2.1.4 '@typescript-eslint/eslint-plugin': 5.47.1_txmweb6yn7coi7nfrp22gpyqmy '@typescript-eslint/parser': 5.47.1_lzzuuodtsqwxnvqeq4g4likcqa eslint: 8.30.0 @@ -370,6 +398,19 @@ packages: - supports-color dev: true + /@sveltestack/svelte-query/1.6.0: + resolution: {integrity: sha512-C0wWuh6av1zu3Pzwrg6EQmX3BhDZQ4gMAdYu6Tfv4bjbEZTB00uEDz52z92IZdONh+iUKuyo0xRZ2e16k2Xifg==} + peerDependencies: + broadcast-channel: ^4.5.0 + peerDependenciesMeta: + broadcast-channel: + optional: true + dev: false + + /@types/chroma-js/2.1.4: + resolution: {integrity: sha512-l9hWzP7cp7yleJUI7P2acmpllTJNYf5uU6wh50JzSIZt3fFHe+w2FM6w9oZGBTYzjjm2qHdnQvI+fF/JF/E5jQ==} + dev: true + /@types/cookie/0.5.1: resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==} dev: true @@ -386,6 +427,10 @@ packages: resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} dev: true + /@types/qs/6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: false + /@types/sass/1.43.1: resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} dependencies: @@ -577,6 +622,20 @@ packages: engines: {node: '>=8'} dev: true + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /axios/1.2.2: + resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -611,6 +670,13 @@ packages: streamsearch: 1.1.0 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 + /callsites/3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -639,6 +705,10 @@ packages: fsevents: 2.3.2 dev: true + /chroma-js/2.4.2: + resolution: {integrity: sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==} + dev: false + /color-convert/2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -650,6 +720,13 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -680,6 +757,10 @@ packages: ms: 2.1.2 dev: true + /dedent-js/1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + dev: false + /deep-is/0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -689,6 +770,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /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==} engines: {node: '>=8'} @@ -959,6 +1045,25 @@ packages: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true + /follow-redirects/1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + /form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true @@ -973,7 +1078,14 @@ packages: /function-bind/1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true + + /get-intrinsic/1.1.3: + resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: false /glob-parent/5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -1040,12 +1152,16 @@ packages: engines: {node: '>=8'} dev: true + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: false + /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 - dev: true /ignore/5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} @@ -1119,6 +1235,10 @@ packages: engines: {node: '>=8'} dev: true + /is-promise/4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + dev: false + /isexe/2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -1142,6 +1262,10 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /jwt-decode/3.1.2: + resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} + dev: false + /kleur/4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -1166,6 +1290,12 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lower-case/2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.4.1 + dev: false + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1199,6 +1329,18 @@ packages: 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==} engines: {node: '>=10.0.0'} @@ -1255,11 +1397,22 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /no-case/3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.4.1 + dev: false + /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} dev: true + /object-inspect/1.12.2: + resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} + dev: false + /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -1299,6 +1452,13 @@ packages: callsites: 3.1.0 dev: true + /pascal-case/3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + dependencies: + no-case: 3.0.4 + tslib: 2.4.1 + dev: false + /path-exists/4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1362,11 +1522,22 @@ packages: hasBin: true dev: true + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} dev: true + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -1480,6 +1651,14 @@ packages: engines: {node: '>=8'} dev: true + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + object-inspect: 1.12.2 + dev: false + /sirv/2.0.2: resolution: {integrity: sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==} engines: {node: '>= 10'} @@ -1550,6 +1729,12 @@ packages: engines: {node: '>= 0.4'} dev: true + /svelecte/3.13.0: + resolution: {integrity: sha512-PwAV9+45+fVJsWFiM+xX+82qKs+GuL1hSUIajnEMMjbomLgoT6b0Z4dcyIRSjtCJrUtbBVQF6UG2Ekx4HFldNA==} + dependencies: + svelte-tiny-virtual-list: 2.0.5 + dev: false + /svelte-check/2.10.3_sass@1.57.1+svelte@3.55.0: resolution: {integrity: sha512-Nt1aWHTOKFReBpmJ1vPug0aGysqPwJh2seM1OvICfM2oeyaA62mOiy5EvkXhltGfhCcIQcq2LoE0l1CwcWPjlw==} hasBin: true @@ -1578,6 +1763,12 @@ packages: - sugarss dev: true + /svelte-forms/2.3.1: + resolution: {integrity: sha512-ExX9PM0JgvdOWlHl2ztD7XzLNPOPt9U5hBKV8sUAisMfcYWpPRnyz+6EFmh35BOBGJJmuhTDBGm5/7seLjOTIA==} + dependencies: + is-promise: 4.0.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} @@ -1587,6 +1778,25 @@ packages: svelte: 3.55.0 dev: true + /svelte-icons/2.1.0: + resolution: {integrity: sha512-rHPQjweEc9fGSnvM0/4gA3pDHwyZyYsC5KhttCZRhSMJfLttJST5Uq0B16Czhw+HQ+HbSOk8kLigMlPs7gZtfg==} + dev: false + + /svelte-multiselect/8.2.3: + resolution: {integrity: sha512-cCnPFkG+0i2eBDaYUOgmQVa2TaJ6Xdjly0/tpch0XCfu4Rs0whbnEXP4QfKVloaAxEDUXwiIq/FHEYZ61xAklg==} + dev: false + + /svelte-navigator/3.2.2_niwyv7xychq2ag6arq5eqxbomm: + resolution: {integrity: sha512-Xio4ohLUG1nQJ+ENNbLphXXu9L189fnI1WGg+2Q3CIMPe8Jm2ipytKQthdBs8t0mN7p3Eb03SE9hq0xZAqwQNQ==} + peerDependencies: + svelte: 3.x + dependencies: + svelte: 3.55.0 + svelte2tsx: 0.1.193_niwyv7xychq2ag6arq5eqxbomm + transitivePeerDependencies: + - typescript + dev: false + /svelte-preprocess/4.10.7_cfliyikhlimajcn5n7qvd3jsli: resolution: {integrity: sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==} engines: {node: '>= 9.11.2'} @@ -1639,10 +1849,36 @@ packages: typescript: 4.9.4 dev: true + /svelte-routing/1.6.0_niwyv7xychq2ag6arq5eqxbomm: + resolution: {integrity: sha512-+DbrSGttLA6lan7oWFz1MjyGabdn3tPRqn8Osyc471ut2UgCrzM5x1qViNMc2gahOP6fKbKK1aNtZMJEQP2vHQ==} + peerDependencies: + svelte: ^3.20.x + dependencies: + svelte: 3.55.0 + svelte2tsx: 0.1.193_niwyv7xychq2ag6arq5eqxbomm + transitivePeerDependencies: + - typescript + dev: false + + /svelte-tiny-virtual-list/2.0.5: + resolution: {integrity: sha512-xg9ckb8UeeIme4/5qlwCrl2QNmUZ8SCQYZn3Ji83cUsoASqRNy3KWjpmNmzYvPDqCHSZjruBBsoB7t5hwuzw5g==} + dev: false + /svelte/3.55.0: resolution: {integrity: sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==} engines: {node: '>= 8'} - dev: true + + /svelte2tsx/0.1.193_niwyv7xychq2ag6arq5eqxbomm: + resolution: {integrity: sha512-vzy4YQNYDnoqp2iZPnJy7kpPAY6y121L0HKrSBjU/IWW7DQ6T7RMJed2VVHFmVYm0zAGYMDl9urPc6R4DDUyhg==} + peerDependencies: + svelte: ^3.24 + typescript: ^4.1.2 + dependencies: + dedent-js: 1.0.1 + pascal-case: 3.1.2 + svelte: 3.55.0 + typescript: 4.9.4 + dev: false /text-table/0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -1673,7 +1909,6 @@ packages: /tslib/2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} - dev: true /tsutils/3.21.0_typescript@4.9.4: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -1701,7 +1936,6 @@ packages: resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} engines: {node: '>=4.2.0'} hasBin: true - dev: true /undici/5.14.0: resolution: {integrity: sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==} diff --git a/frontend/src/apis/exo.api.ts b/frontend/src/apis/exo.api.ts new file mode 100644 index 0000000..e13b439 --- /dev/null +++ b/frontend/src/apis/exo.api.ts @@ -0,0 +1,15 @@ +import { browser } from '$app/environment'; +import axios from 'axios'; +import { parse, stringify } from 'qs' +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`, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Access-Control-Allow-Origin': '*', + //'X-CSRFToken': csrftoken != undefined ? csrftoken : '', + ...(browser && + localStorage.getItem('token') && { Authorization: `Bearer ${localStorage.getItem('token')}` }) + } +}); diff --git a/frontend/src/app.scss b/frontend/src/app.scss index 4e69b99..6b2d9d1 100644 --- a/frontend/src/app.scss +++ b/frontend/src/app.scss @@ -1,4 +1,82 @@ /* Write your global styles here, in SCSS syntax. Variables and mixins from the src/variables.scss file are available here without importing */ -.input{ - background-color: red; + +* { + box-sizing: border-box; + margin: 0; +} + +.btn { + border: none; + border-radius: 5px; + height: 38px; + font-weight: 700; + transition: 0.3s; + margin-bottom: 10px; + margin-right: 7px; + padding: 0 10%; + width: max-content; + cursor: pointer; +} + +.primary-btn { + @extend .btn; + background-color: #fcbf49; + &:hover { + background-color: #ac7b19; + } +} + +.danger-btn { + @extend .btn; + background-color: #fc5e49; + &:hover { + background-color: #ac1919; + } +} + +.border-primary-btn { + @extend .btn; + background-color: transparent; + border: 1px solid #fcbf49; + color: #fcbf49; + &:hover { + background-color: #fcbf49; + color: black; + } +} + +.input { + background-color: inherit; + color: inherit; + padding: 5px 10px; + width: 100%; + font-size: 16px; + font-weight: 450; + margin: 10px 0; + float: left; + border: none; + border-bottom: 1px solid #181553; + transition: 0.3s; + border-radius: 0; + margin: 0; + &:focus { + outline: none; + border-bottom-color: red; + } +} + + +.flex-row-center { + display: flex; + justify-content: center; +} +@for $f from 0 through 100 { + .wp-#{$f} { + width: 1% * $f; + } +} + + +.sv-dropdown{ + z-index: 10!important; } \ No newline at end of file diff --git a/frontend/src/components/exos/Card.svelte b/frontend/src/components/exos/Card.svelte index 31a28ee..c2b3281 100644 --- a/frontend/src/components/exos/Card.svelte +++ b/frontend/src/components/exos/Card.svelte @@ -1,49 +1,143 @@ -
-

{title}

+
{}} + on:keypress={() => {}} +> +

{exo.name}

Exemples

- {#each examples.slice(0, 3) as ex} -

{ex}

+ {#if !!exo.consigne}

{exo.consigne}

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

{ex.calcul}

{/each}
-
{ - alert('test'); - }} - /> + {#if !!isAuth} + {#if exo.is_author && exo.original == null} +
+ + {exo.private == true ? 'Privé' : 'Public'} +
+ {:else if !exo.is_author} +
+ + Par {exo.author.username} + +
{}} + on:click|stopPropagation={() => { + cloneExo(exo.id_code).then((r) => { + goto('/exercices/' + r.id_code); + show(ModalCard, { exo: r }, () => { + goto('/exercices/user'); + }); + }); + }} + > + +
+
+ {:else if exo.is_author && exo.original != null} +
+ Par {exo.original?.author} +
+ {/if}{/if}
+ {#if !!isAuth} + + {/if} +
diff --git a/frontend/src/components/exos/DownloadForm.svelte b/frontend/src/components/exos/DownloadForm.svelte new file mode 100644 index 0000000..ce1a716 --- /dev/null +++ b/frontend/src/components/exos/DownloadForm.svelte @@ -0,0 +1,199 @@ + + +{#if exo != null} +

+ {exo.name} + {#if exo.is_author && exo.original == null} + {exo.private == true ? 'Privé' : 'Public'} + {:else if exo.is_author && exo.original != null} + { + if (exo.original == null) return; + + getExo(exo.original?.id_code).then((r) => { + goto(`/exercices/${exo.original?.id_code}`); + show( + ModalCard, + { exo: r }, + () => { + goto('/exercices/user'); + }, + true + ); + }); + }} + on:keyup={() => {}} + > + Exercice original de {exo.original?.author} + + {:else if !exo.is_author && exo.original == null} + + Par {exo.author.username} + + {/if} +

+ + +
+

Exemples

+ {#if !!exo.consigne}

{exo.consigne}

{/if} + + {#each exo.examples.data as e} +

{e.calcul}

+ {/each} +
+
+ +
+
+ + {#if !!isAuth} + + {/if} +
+
+
close()} on:keypress={() => {}}> + +
+
+ +
+ {#if !!isAuth} + {#if exo.is_author} +
{ + alert({ + title: 'Sur ?', + description: 'Voulez vous supprimer ? ', + validate: () => { + close(); + delete_(); + } + }); + }} + on:keypress={() => {}} + > + +
+
edit()} on:keypress={() => {}}> + +
+ {:else} +
{ + cloneExo(exo.id_code).then((r) => { + goto('/exercices/' + r.id_code); + show( + ModalCard, + { exo: r }, + () => { + goto('/exercices/user'); + }, + true + ); + }); + }} + on:keypress={() => {}} + title="Copier l'exercice pour pouvoir le modifier" + > + +
+ {/if} + {/if} +
+
+{/if} + + diff --git a/frontend/src/components/exos/EditForm.svelte b/frontend/src/components/exos/EditForm.svelte new file mode 100644 index 0000000..d6e0237 --- /dev/null +++ b/frontend/src/components/exos/EditForm.svelte @@ -0,0 +1,118 @@ + + +
{ + if (editing && exo != null) { + editExo(exo.id_code, { + name: $name.value, + consigne: $consigne.value, + private: $prv.value, + ...($model.dirty == true && { file: $model.value[0] }) + }).then((r) => { + exo=r.data + updateExo(r.data); + cancel() + }); + } else { + createExo({ + name: $name.value, + consigne: $consigne.value, + private: $prv.value, + file: $model.value[0] + }).then((r) => { + updateExo(r.data); + cancel() + }); + } + }} +> + + + + +
+ + +
+ + +
+ + +
+ + + diff --git a/frontend/src/components/exos/Feed.svelte b/frontend/src/components/exos/Feed.svelte index 83b92c4..77064fa 100644 --- a/frontend/src/components/exos/Feed.svelte +++ b/frontend/src/components/exos/Feed.svelte @@ -1,31 +1,89 @@ - + +{#if $tagStore.isSuccess == true && $tagStore.data != undefined} + +{/if} +{#if $tagStore.isFetching == true} + Fetching +{/if} +

@@ -36,9 +94,14 @@ publics

- {#each exos as e} - - {/each} + {#if $exerciceStore.data != undefined} + {#each $exerciceStore.data.items.filter((e) => e != null && selected.every((t) => e.tags + .map((s) => s.id_code) + .includes(t.id_code))) as e} + + {/each} + + {/if}
diff --git a/frontend/src/components/exos/ModalCard.svelte b/frontend/src/components/exos/ModalCard.svelte index fa9c7ea..03f5782 100644 --- a/frontend/src/components/exos/ModalCard.svelte +++ b/frontend/src/components/exos/ModalCard.svelte @@ -1,44 +1,77 @@ -