migrating to svelte

This commit is contained in:
Kilton937342 2022-12-27 18:26:05 +01:00
parent 96a12939c8
commit a819d952d3
4926 changed files with 2134272 additions and 2101 deletions

View File

@ -7,7 +7,7 @@ from generateur.generateur_main import generate_from_data, generate_from_path
from database.auth.models import User
from database.db import get_session
from fastapi import Depends
from database.exercices.models import ExampleEnum, ExerciceCreate, Exercice, ExerciceEdit, ExerciceRead, ExercicesTagLink, Tag, TagCreate, Supports
from database.exercices.models import ExampleEnum, ExerciceCreate, Exercice, ExerciceEdit, ExerciceRead, ExercicesTagLink, Tag, TagCreate, Supports, ExerciceReadFull
from services.auth import get_current_user, get_current_user_optional
from services.database import generate_unique_code
from services.io import add_fast_api_root, get_ancestor, get_or_create_dir
@ -185,9 +185,9 @@ 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: Exercice, user_id: User = None, db: Session):
def serialize_exo(*, exo: ExerciceRead, 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 ExerciceRead(**exo.dict(), author=exo.author, original=exo.original, tags=tags, is_author=is_author, supports={**exo.dict()})
return ExerciceReadFull(**exo.dict(), author=exo.author, original=exo.original, tags=tags, is_author=is_author, supports={**exo.dict()})

View File

@ -144,10 +144,8 @@ class Author(SQLModel):
def get_source_path_from_name_and_id(name: str, id_code: str):
return f'/uploads/{id_code}/{name}'
class ExerciceRead(ExerciceBase):
class ExerciceReadBase(ExerciceBase):
id_code: str
author: Author
original: ExerciceOrigin | None
@ -158,14 +156,23 @@ class ExerciceRead(ExerciceBase):
examples: Example = None
is_author: bool = None
supports: Supports
@validator('exo_source')
def get_exo_source_name(cls, value, values):
if value is not None:
return get_filename_from_path(value)
return value
class ExerciceRead(ExerciceBase):
id: int
author_id:int
author: User
original: Optional[Exercice]
tags: List[Tag]
class ExerciceReadFull(ExerciceReadBase):
supports: Supports

View File

@ -7,7 +7,7 @@ from database.auth.crud import create_user_db
from services.auth import get_current_user_optional, jwt_required
from fastapi.openapi.utils import get_openapi
from database.auth.models import User, UserRead
from database.exercices.models import Exercice, ExerciceRead
from database.exercices.models import Exercice, ExerciceReadFull
from fastapi_pagination import add_pagination
from fastapi.responses import PlainTextResponse
from fastapi.exceptions import RequestValidationError, ValidationError
@ -161,9 +161,9 @@ def refresh_revoke(Authorize: AuthJWT = Depends()):
class user(UserRead):
exercices: List[ExerciceRead] = None
exercices: List[ExerciceReadFull] = None
@app.post('/test', response_model=List[ExerciceRead] )
@app.post('/test', response_model=List[ExerciceReadFull] )
def test(db:Session= Depends(get_session)):
#create_user_db('lilian', get_password_hash('Pomme937342'), db)
create_user_db('lilian2', get_password_hash('Pomme937342'), db)

View File

@ -3,7 +3,7 @@ from typing import List
from fastapi import APIRouter, Depends, Path, Query, UploadFile, HTTPException, status
from database.auth.models import User
from database.db import get_session
from database.exercices.models import Exercice, ExerciceCreate, ExerciceEdit, ExerciceRead, ExercicesTagLink, Tag, TagCreate, TagRead
from database.exercices.models import Exercice, ExerciceCreate, ExerciceEdit, ExerciceReadFull, ExercicesTagLink, Tag, TagCreate, TagRead, ExerciceRead
from services.auth import get_current_user, get_current_user_optional
from sqlmodel import Session, select, col
from database.exercices.crud import add_tags_db, check_exercice_author, check_private, check_tag_author, create_exo_db, delete_exo_db, get_exo_dependency, clone_exo_db, parse_exo_tags, remove_tag_db, serialize_exo, update_exo_db, get_tags_dependency
@ -11,7 +11,8 @@ 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.ext.sqlalchemy_future import paginate as p
router = APIRouter(tags=['exercices'])
class ExoType(str, Enum):
@ -30,7 +31,7 @@ def queryFilters_dependency(search: str = "", tags: List[int] | None = Depends(g
return search, tags, type
@router.post('/exercices', response_model=ExerciceRead, status_code=status.HTTP_201_CREATED)
@router.post('/exercices', response_model=ExerciceReadFull, status_code=status.HTTP_201_CREATED)
def create_exo(exercice: ExerciceCreate = Depends(ExerciceCreate.as_form), file: UploadFile = Depends(validate_file), user: User = Depends(get_current_user), db: Session = Depends(get_session)):
file_obj = file['file'].file._file
file_obj.name = file['file'].filename
@ -39,7 +40,7 @@ def create_exo(exercice: ExerciceCreate = Depends(ExerciceCreate.as_form), file:
return serialize_exo(exo=exo_obj, user_id=user.id, db=db)
@router.post('/clone/{id_code}', response_model=ExerciceRead)
@router.post('/clone/{id_code}', response_model=ExerciceReadFull)
def clone_exo(exercice: Exercice | None = Depends(check_private), user: User = Depends(get_current_user), db: Session = Depends(get_session)):
if not exercice:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail={
@ -51,11 +52,10 @@ def clone_exo(exercice: Exercice | None = Depends(check_private), user: User = D
return serialize_exo(exo=exo_obj, user_id=user.id, db=db)
@router.get('/exercices/user', response_model=List[ExerciceRead])
@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)):
search, tags, type = queryFilters
if tags is not None and len(tags) != 0:
statement = select(Exercice, func.group_concat(
ExercicesTagLink.tag_id))
@ -76,16 +76,18 @@ def get_user_exercices(user: User = Depends(get_current_user), queryFilters: tup
statement = statement.join(ExercicesTagLink).where(
col(ExercicesTagLink.tag_id).in_(tags)).group_by(ExercicesTagLink.exercice_id)
exercices = db.exec(statement).all()
#exercices = db.exec(statement).all()
page = p(db, statement)
exercices = page.items
if tags is not None and len(tags) != 0:
exercices = filter_exo_by_tags(exercices, tags)
exercices = [
serialize_exo(exo=e, user_id=user.id, db= db) for e in exercices]
return exercices
page.items = [
serialize_exo(exo=e, user_id=user.id, db=db) for e in exercices]
return page
@router.get('/exercices/public', response_model=List[ExerciceRead])
@router.get('/exercices/public', response_model=Page[ExerciceRead|ExerciceReadFull])
def get_public_exercices(user: User | None = Depends(get_current_user_optional), queryFilters: tuple[str, List[int] | None] = Depends(queryFilters_dependency), db: Session = Depends(get_session)):
search, tags, type = queryFilters
@ -130,15 +132,15 @@ def get_public_exercices(user: User | None = Depends(get_current_user_optional),
if type == ExoType.web:
statement = statement.where(Exercice.web == True)
exercices = db.exec(statement).all()
return [serialize_exo(exo=e, user_id=None, db=db) for e in exercices]
return paginate([serialize_exo(exo=e, user_id=None, db=db) for e in exercices])
@router.get('/exercice/{id_code}', response_model=ExerciceRead)
@router.get('/exercice/{id_code}', response_model=ExerciceReadFull)
def get_exercice(exo: Exercice = Depends(check_private), user: User | None = Depends(get_current_user_optional), db: Session = Depends(get_session)):
return serialize_exo(exo=exo, user_id=getattr(user, 'id', None), db=db)
@router.put('/exercice/{id_code}', response_model=ExerciceRead)
@router.put('/exercice/{id_code}', response_model=ExerciceReadFull)
def update_exo(file: UploadFile = Depends(validate_file_optionnal), exo: Exercice = Depends(check_exercice_author), exercice: ExerciceEdit = Depends(ExerciceEdit.as_form), db: Session = Depends(get_session)):
if exo is None:
raise HTTPException(
@ -167,7 +169,7 @@ 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=ExerciceRead, tags=['tags'])
@router.post('/exercice/{id_code}/tags', response_model=ExerciceReadFull, 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(
@ -176,7 +178,7 @@ def add_tags(tags: List[TagCreate], exo: Exercice | None = Depends(get_exo_depen
return serialize_exo(exo=exo_obj, user_id=user.id, db=db)
@router.delete('/exercice/{id_code}/tags/{tag_id}', response_model=ExerciceRead, tags=['tags'])
@router.delete('/exercice/{id_code}/tags/{tag_id}', response_model=ExerciceReadFull, tags=['tags'])
def remove_tag(exo: Exercice | None = Depends(get_exo_dependency), tag: Tag | None | bool = Depends(check_tag_author), db: Session = Depends(get_session)):
if exo is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,

View File

@ -337,7 +337,60 @@ def test_get_users_exos(client: TestClient):
r = client.get('/exercices/user',
headers={'Authorization': 'Bearer ' + token1})
print(r.json())
assert r.json() == [prv]
assert r.json()['page'] == 1
assert r.json()['size'] == 50
assert r.json()["items"] == [prv]
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"})
r = client.get('/exercices/user',
headers={'Authorization': 'Bearer ' + token1}, params={"page": 2, "size": 10})
print(r.json())
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"})
r = client.get('/exercices/user',
headers={'Authorization': 'Bearer ' + token1}, params={"page": 2, "size": 10})
print(r.json())
assert r.json()['page'] == 2
assert r.json()['size'] == 10
assert r.json()["items"] == []
def test_get_user_with_search(client: TestClient):
@ -361,7 +414,7 @@ def test_get_user_with_search(client: TestClient):
r = client.get('/exercices/user', params={"search": "test"},
headers={'Authorization': 'Bearer ' + token1})
print(r.json())
assert r.json() == [exo1, exo2]
assert r.json()['items'] == [exo1, exo2]
def test_get_user_with_search(client: TestClient):
@ -385,7 +438,7 @@ def test_get_user_with_search(client: TestClient):
r = client.get('/exercices/user', params={"search": "test"},
headers={'Authorization': 'Bearer ' + token1})
print(r.json())
assert r.json() == [exo1, exo2]
assert r.json()['items'] == [exo1, exo2]
def test_get_user_with_tags(client: TestClient):
@ -415,7 +468,7 @@ def test_get_user_with_tags(client: TestClient):
r = client.get('/exercices/user', params={'tags': [*[t['id_code'] for t in tags2], 'notexist']},
headers={'Authorization': 'Bearer ' + token1})
print(r.json())
assert r.json() == [exo2, exo3]
assert r.json()['items'] == [exo2, exo3]
def test_get_user_with_tags_and_search(client: TestClient):
@ -446,7 +499,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() == [exo3]
assert r.json()['items'] == [exo3]
def test_get_public_auth(client: TestClient):
@ -463,7 +516,7 @@ def test_get_public_auth(client: TestClient):
r = client.get('/exercices/public',
headers={'Authorization': 'Bearer ' + token2})
print(r.json())
assert r.json() == [{**public2, 'is_author': False}]
assert r.json()['items'] == [{**public2, 'is_author': False}]
def test_get_public_auth_with_search(client: TestClient):
@ -483,7 +536,7 @@ def test_get_public_auth_with_search(client: TestClient):
r = client.get('/exercices/public',
params={'search': "yes"}, headers={'Authorization': 'Bearer ' + token2})
print(r.json())
assert r.json() == [{**public2, 'is_author': False}]
assert r.json()['items'] == [{**public2, 'is_author': False}]
def test_get_public_no_auth(client: TestClient):
@ -499,7 +552,7 @@ def test_get_public_no_auth(client: TestClient):
r = client.get('/exercices/public')
print(r.json())
assert r.json() == [{**public1, 'is_author': False},
assert r.json()['items'] == [{**public1, 'is_author': False},
{**public2, 'is_author': False}]
@ -508,7 +561,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() == {**exo, "tags": [], 'is_author': False}
assert r.json()['items'] == {**exo, "tags": [], 'is_author': False}
def test_get_exo_no_auth_private(client: TestClient):

13
frontend/.eslintignore Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

20
frontend/.eslintrc.cjs Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
};

10
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
frontend/.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

13
frontend/.prettierignore Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

9
frontend/.prettierrc Normal file
View File

@ -0,0 +1,9 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@ -1,34 +1,38 @@
## Usage
# create-svelte
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
$ npm install # or pnpm install or yarn install
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Developing
## Available Scripts
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
In the project directory, you can run:
```bash
npm run dev
### `npm dev` or `npm start`
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
## Building
The page will reload if you make edits.<br>
To create a production version of your app:
### `npm run build`
```bash
npm run build
```
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
You can preview the production build with `npm run preview`.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## Deployment
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View File

@ -1,33 +1,35 @@
{
"name": "vite-template-solid",
"version": "0.0.0",
"description": "",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"license": "MIT",
"devDependencies": {
"sass": "^1.54.3",
"typescript": "^4.7.4",
"vite": "^3.0.0",
"vite-plugin-solid": "^2.3.0"
},
"dependencies": {
"@solidjs/meta": "^0.28.0",
"@solidjs/router": "^0.4.2",
"axios": "^0.27.2",
"chroma-js": "^2.4.2",
"emotion-solid": "^1.1.1",
"jwt-decode": "^3.1.2",
"solid-forms": "^0.4.5",
"solid-icons": "^1.0.1",
"solid-js": "^1.4.7",
"solid-styled-components": "^0.28.4",
"solid-styled-jsx": "^0.27.1",
"solid-toast": "^0.3.4",
"styled-jsx": "^3.4.4"
}
"name": "frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "playwright test",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:unit": "vitest",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^1.0.0",
"@sveltejs/kit": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0",
"svelte-check": "^2.9.2",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vitest": "^0.25.3"
},
"type": "module"
}

View File

@ -0,0 +1,11 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
},
testDir: 'tests'
};
export default config;

File diff suppressed because it is too large Load Diff

9
frontend/src/app.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}

12
frontend/src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -1,73 +0,0 @@
import { Component, createSignal } from "solid-js";
import Section from "./section";
import { FaSolidLock } from "solid-icons/fa";
import TextField from "../forms/TextField";
import { createStore } from "solid-js/store";
import TextField2 from "../forms/TextField2";
import styles from "../../styles/auth/changePassword.module.scss";
import { Modal } from "../Modal";
import { DeleteConfirm } from "./DeleteConfirm";
import { update_password } from "../../requests/auth.requests.js";
import { useLoginPopup } from "../../context/loginPopUp.context.jsx";
import { useForm } from "../../hooks/useForm.js";
export const ChangePasswordForm: Component = () => {
const [fields, setFields] = createStore({
password: "",
password_confirm: "",
});
const { form, setFieldsErrors, getFieldController } = useForm({
password: "",
password_confirm: "",
});
const loginPopup = useLoginPopup();
return (
<div>
<Section name="Sécurité" icon={<FaSolidLock />}>
<form class={styles.form}>
<TextField2
placeholder="Mot de passe..."
control={getFieldController("password")}
type="password"
/>
<TextField2
placeholder="Confirmation"
control={getFieldController("password_confirm")}
type="password"
/>
</form>
</Section>
<button
class={`exo-btn ${styles.submit}`}
onclick={() => {
var data = new URLSearchParams({
password: form.password,
password_confirm: form.password_confirm,
});
update_password(data)
.then((r) => {
console.log(r);
setFieldsErrors({});
})
.catch((err) => {
console.log("r", err);
var errs = err.response.data.detail;
if (errs == "Fresh token required") {
loginPopup.popup(() => {
update_password(data).catch(() => {
var errs = err.response.data.detail;
if (err.response.status == 422) {
setFieldsErrors(errs);
}
});
});
} else if (err.response.status == 422) {
setFieldsErrors(errs);
}
});
}}
>
Modifier mon mot de passe
</button>
</div>
);
};

View File

@ -1,53 +0,0 @@
import { Component, createSignal } from "solid-js";
import styles from "../../styles/auth/deleteConfirm.module.scss";
import TextField2 from "../forms/TextField2";
import jwtDecode from "jwt-decode";
import {
delete_user, revoke_all
} from "../../requests/auth.requests.js";
import { useNavigate } from "@solidjs/router";
export const DeleteConfirm: Component = () => {
const [password, setPassword] = createSignal("");
const [error, setError] = createSignal("");
const navigate = useNavigate()
return (
<div class={styles.main}>
<h1 class={styles.title}>Confirmer la suppression</h1>
<p>Vous êtes sur le point de supprimer votre compte.</p>
<p>Toutes vos salles seront supprimées.</p>
<p>Cette action est irréversible ! </p>
<TextField2
control={{
error: error(),
value: password(),
setValue: (v) => {
setPassword(v);
},
}}
placeholder="Entrez votre mot de passe pour confirmer"
type="password"
/>
<button
class={`exo-btn ${styles.delete}`}
onclick={() => {
var token = localStorage.getItem("token");
if (token != null) {
var decoded = jwtDecode<{sub:string}>(token);
var username = decoded.sub
var data = new URLSearchParams({ 'username': username, password: password() })
delete_user(data).then(() => {
revoke_all()
navigate('/login')
}).catch(() => {
setError("Erreur lors de l'authentification")
})
}
}}
>
Supprimer mon compte
</button>
</div>
);
};

View File

@ -1,100 +0,0 @@
import {
withControl,
createFormGroup,
createFormControl,
IFormGroup,
IFormControl,
} from "solid-forms";
import TextField from "../forms/TextField";
import styles from "../../styles/auth/editUser.module.scss";
import { useAuth } from "../../context/auth.context.jsx";
import { useNavigate } from "@solidjs/router";
import TextField2 from "../forms/TextField2";
import { Component, createEffect } from "solid-js";
import Section from "./section";
import { FaSolidUser } from "solid-icons/fa";
import { createStore } from "solid-js/store";
import { update_user, revoke_all } from "../../requests/auth.requests.js";
import { useForm } from "../../hooks/useForm.js";
import { User } from "../../types/auth.type";
const EditUserForm: Component<{ user: User | undefined }> = (props) => {
const { form, setFieldsErrors, getFieldController, setFieldValue } = useForm({
email: "",
username: "",
firstname: "",
name: "",
});
createEffect(() => {
setFieldValue("username", props.user?.username);
setFieldValue("email", props.user?.email);
setFieldValue("firstname", props.user?.firstname);
setFieldValue("name", props.user?.name);
});
return (
<div class={styles.main}>
<div>
<Section name="Infos" icon={<FaSolidUser size={24} />}>
<form class={styles.form}>
<TextField2
label="Username"
placeholder="Username..."
control={getFieldController("username")}
type="text"
/>
<TextField2
label="Email"
placeholder="Email..."
control={getFieldController("email")}
type="text"
/>
<TextField2
label="Prénom"
placeholder="Prénom..."
control={getFieldController("firstname")}
type="text"
/>
<TextField2
label="Nom"
placeholder="Nom..."
control={getFieldController("name")}
type="text"
/>
</form>
</Section>
</div>
<button
onclick={() => {
var data = new URLSearchParams({
email: form.email == null ? "" : form.email,
username: form.username,
firstname: form.firstname == null ? "" : form.firstname,
name: form.name == null ? "" : form.name,
});
update_user(data)
.then((r) => {
console.log(r);
revoke_all();
localStorage.setItem("token", r.access_token);
localStorage.setItem("refresh token", r.refresh_token);
setFieldsErrors({});
})
.catch((err) => {
var errs = err.response.data.detail;
if (err.response.status == 422) {
setFieldsErrors(errs);
}
});
}}
class={`${styles.submit} exo-btn`}
>
Modifier
</button>
</div>
);
};
export default EditUserForm;

View File

@ -1,50 +0,0 @@
import { withControl, createFormGroup, createFormControl } from "solid-forms";
import TextField from "../forms/TextField";
import styles from "../../styles/auth/login.module.scss";
import { dispatchAuth } from "../../context/auth.context.jsx";
import { useNavigate } from "@solidjs/router";
import { Component } from "solid-js";
import { createStore } from "solid-js/store";
import { useLoginPopup } from "../../context/loginPopUp.context.jsx";
import { useForm } from "../../hooks/useForm.js";
import { login_request } from "../../requests/auth.requests.js";
import { useAuth } from "../../context/auth.context.jsx";
export const LoginForm: Component = () => {
const auth = useAuth();
const { form, setFieldsErrors, getFieldController } = useForm({
username: "",
password: "",
});
return (
<form
class={styles["main"]}
onsubmit={(e) => {
e.preventDefault();
var data = new URLSearchParams({
username: form.username,
password: form.password,
});
auth.login(form.username, form.password)
.catch((err) => {
var errs = err.response.data.detail;
setFieldsErrors(errs);
});
}}
>
<TextField
label="Username"
control={getFieldController("username")}
type="text"
required={true}
/>
<TextField
label="Password"
control={getFieldController("password")}
type="password"
required={true}
/>
<button class={`exo-btn ${styles.submit}`}>Se connecter</button>
</form>
);
};

View File

@ -1,57 +0,0 @@
import { useNavigate } from "@solidjs/router";
import { Component } from "solid-js";
import { useForm } from "../../hooks/useForm.js";
import { register_request } from "../../requests/auth.requests.js";
import TextField from "../forms/TextField";
import styles from '../../styles/auth/login.module.scss';
import { useAuth } from "../../context/auth.context.jsx";
export const RegisterForm: Component = () => {
const { form, setFieldValue, setFieldsErrors, getFieldController } = useForm({
username: "",
password: "",
password_confirm: "",
});
const navigate = useNavigate();
const auth = useAuth()
return (
<form
class={styles.main}
onsubmit={(e) => {
e.preventDefault();
var data = new URLSearchParams({
username: form.username,
password: form.password,
password_confirm: form.password_confirm,
});
auth.signup(form.username, form.password, form.password_confirm)
.catch((err) => {
var errs = err.response.data.detail;
if (err.response.status == 422) {
setFieldsErrors(errs);
}
});
}}
>
<TextField
type="text"
control={getFieldController("username")}
label="Username"
required={true}
/>
<TextField
type="password"
control={getFieldController("password")}
label="Mot de passe"
required={true}
/>
<TextField
type="password"
control={getFieldController("password_confirm")}
label="Confirmation"
required={true}
/>
<button class={`exo-btn ${styles.submit}`}>S'inscrire</button>
</form>
);
}

View File

@ -1,11 +0,0 @@
import { Component, JSX } from "solid-js";
import styles from '../../styles/auth/section.module.scss';
const Section: Component<{name:string, icon: JSX.Element, children: JSX.Element}> = (props) => {
return <div class={styles['main']}>
<h2 class={`marginb-p1 ${styles.title}`}>{props.icon}{' '}{props.name}</h2>
<div class={styles["child"]}>{props.children}</div>
{/* {validate && validateMsg && <button class={parseclass(['exo-btn', styles['btn']])} onClick={validate}>{validateMsg}</button>} */}
</div>
}
export default Section

View File

@ -1,147 +0,0 @@
import { useNavigate } from "@solidjs/router";
import jwtDecode from "jwt-decode";
import { useContext } from "solid-js";
import { createSignal } from "solid-js";
import { Show } from "solid-js";
import { createEffect } from "solid-js";
import { createContext } from "solid-js";
import { createStore } from "solid-js/store";
import toast from "solid-toast";
import { setSourceMapRange } from "typescript";
import {
check_access,
get_user,
login_request,
refresh_request,
register_request,
revoke_access,
revoke_refresh,
} from "../requests/auth.requests.js";
import { useLoginPopup } from "./loginPopUp.context.jsx";
import { AiFillAndroid } from 'solid-icons/ai'
import { useNotification } from "./notification.context.jsx";
const AuthContext = createContext();
const jwt_expire_check = (token) => {
var { exp } = jwtDecode(token);
return Date.now() >= exp * 1000;
};
export function AuthProvider(props) {
const [username, setUsername] = createSignal(null);
const [isAuthenticated, setAuthenticated] = createSignal(false);
const [loading, setLoading] = createSignal(false);
const [initialLoading, setInitialLoading] = createSignal(true);
const navigate = useNavigate();
const notification = useNotification()
const popup = useLoginPopup();
createEffect(() => {
var token = localStorage.getItem("token");
var refresh = localStorage.getItem("refresh_token");
if (token != null) {
var is_expired = jwt_expire_check(token);
if (is_expired && refresh != null) {
refresh_request(refresh)
.then((r) => {
localStorage.setItem("token", r.access_token);
return r.access_token;
})
.then((t) => {
setAuthenticated(true);
var { sub } = jwtDecode(t);
setUsername(sub);
}).catch((err) => {
if ( !!err.response.status && err.reponse.status == 422) {
localStorage.removeItem("refresh_token");
localStorage.removeItem("token");
}
});
} else {
check_access(token)
.then(() => {
setAuthenticated(true);
var { sub } = jwtDecode(token);
setUsername(sub);
})
.catch((err) => {
if (err.reponse.status == 422) {
localStorage.removeItem("refresh_token");
localStorage.removeItem("token");
}
});
}
}
setInitialLoading(false);
});
const login = (username, password) => {
var data = new URLSearchParams({ username, password });
return login_request(data).then((r) => {
localStorage.setItem("token", r.access_token);
localStorage.setItem("refresh_token", r.refresh_token);
var decoded = jwtDecode(r.access_token);
setAuthenticated(true);
setUsername(decoded.sub);
if (popup.active == true) {
popup.next();
} else {
navigate("/dashboard");
}
});
};
const signup = (username, password, password2) => {
var data = new URLSearchParams({
username: username,
password: password,
password_confirm: password2,
});
register_request(data).then((r) => {
localStorage.setItem("token", r.access_token);
localStorage.setItem("refresh_token", r.refresh_token);
var decoded = jwtDecode(r.access_token);
setAuthenticated(true);
setUsername(decoded.sub);
navigate("/dashboard");
});
};
const logout = () => {
var token = localStorage.getItem("token");
var refresh = localStorage.getItem("refresh_token");
revoke_access(token)
.then(() => {
return revoke_refresh(refresh);
})
.then(() => {
localStorage.removeItem("token");
localStorage.removeItem("refresh_token");
setUsername(null)
setAuthenticated(false)
navigate("/login");
notification.success('Déconnexion', "Vous n'êtes plus connecté !")
});
};
return (
<AuthContext.Provider
value={{
username: username,
isAuthenticated: isAuthenticated,
loading: loading,
login,
signup,
logout,
}}
>
<Show when={!initialLoading()}>{props.children}</Show>
</AuthContext.Provider>
);
}
export const useAuth = () => useContext(AuthContext);

View File

@ -0,0 +1,7 @@
import { describe, it, expect } from 'vitest';
describe('sum test', () => {
it('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
});

View File

@ -1,126 +0,0 @@
import { authInstance } from "../apis/auth.instance.js"
export const login_request= (data)=> {
return authInstance
.request({
url: "/login",
method: "post",
data: data,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
})
.then((r) => r.data);
}
export const register_request= (data)=> {
return authInstance
.request({
url: "/register",
method: "post",
data: data,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
})
.then((r) => r.data).catch((err)=> {throw err});
}
export const refresh_request= (token)=> {
return authInstance
.request({
url: "/refresh",
method: "post",
headers: { "Authorization": "Bearer " + token },
})
.then((r) => r.data);
}
export const revoke_access = (token)=> {
return authInstance
.request({
url: "/access-revoke",
method: "delete",
headers: { "Authorization": "Bearer " + token },
})
.then((r) => r.data);
}
export const check_access = (token)=> {
return authInstance
.request({
url: "/check-access",
method: "post",
headers: { "Authorization": "Bearer " + token },
})
.then((r) => r.data);
}
export const revoke_refresh = (token)=> {
return authInstance
.request({
url: "/refresh-revoke",
method: "delete",
headers: { "Authorization": "Bearer " + token },
})
.then((r) => r.data);
}
export const revoke_all = () => {
revoke_access(localStorage.getItem('token'))
revoke_refresh(localStorage.getItem('refresh_token'))
}
export const get_user = () => {
return authInstance
.request({
url: "/user",
method: "get",
headers: {
...(localStorage.getItem('token') != null && {Authorization: "Bearer " + localStorage.getItem('token')}),
},
})
.then((r) => r.data);
}
export const update_user = (data) => {
return authInstance
.request({
url: "/user",
method: "put",
data: data,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
...(localStorage.getItem("token") != null && {
Authorization: "Bearer " + localStorage.getItem("token"),
}),
},
})
.then((r) => r.data);
}
export const update_password = (data) => {
return authInstance
.request({
url: "/user/password",
method: "put",
data: data,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
...(localStorage.getItem("token") != null && {
Authorization: "Bearer " + localStorage.getItem("token"),
}),
},
})
.then((r) => r.data);
}
export const delete_user = (data) => {
return authInstance
.request({
url: "/user/",
method: "delete",
data: data,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
...(localStorage.getItem("token") != null && {
Authorization: "Bearer " + localStorage.getItem("token"),
}),
},
})
.then((r) => r.data);
}

View File

@ -0,0 +1,2 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

View File

@ -1,43 +0,0 @@
export type Tag = {
label: string;
color: string;
id_code: string;
};
export type ExerciceShort = {
id_code: string;
name: string;
consigne: string;
pdfSupport: boolean;
csvSupport: boolean;
webSupport: boolean;
tags: Array<Tag>;
examples: { type: string; data: Array<{ calcul: string }> };
};
export type Author = {
username: string;
};
type supports = {
pdf: boolean;
csv: boolean;
web: boolean;
};
export type Exercice = {
id_code: string;
name: string;
consigne: string;
supports: supports;
tags: Array<Tag>;
is_author: boolean;
examples: {
type: string;
data: Array<{ calcul: string; correction: string }>;
};
private: boolean;
exo_source_name: string;
author: Author;
origin: { id_code: string } | null;
};

BIN
frontend/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

15
frontend/svelte.config.js Normal file
View File

@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite'