init project cleaned
This commit is contained in:
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
medias/
|
||||
tmp/
|
||||
target/
|
||||
persistence-db/
|
||||
staticbuild/
|
||||
static/
|
||||
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
www/*/
|
||||
!www/default/
|
||||
!www/static/
|
||||
!www/home/
|
||||
|
||||
/target/
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
/medias
|
||||
persistence-db
|
||||
tmp/*
|
||||
static/sourcefiles/
|
||||
staticbuild/
|
||||
32
Cargo.toml
Normal file
32
Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "dr_who_website"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.13.0"
|
||||
actix-web = "4.2.1"
|
||||
actix-files = "0.6"
|
||||
actix-identity = "0.4"
|
||||
actix-multipart = "0.4"
|
||||
actix-form-data = "0.6.2"
|
||||
actix-web-actors = "4.1.0"
|
||||
oauth2 = "4.2.3"
|
||||
jsonwebtoken-google = "0.1.6"
|
||||
diesel = { version = "1.4.4", features = ["postgres"] }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.59"
|
||||
quick-xml = { version = "0.26.0", features = ["serialize"] }
|
||||
# opencv = "0.73"
|
||||
rand = "0.8.5"
|
||||
dotenv = "0.15.0"
|
||||
sha256 = "1.0.3"
|
||||
walkdir = "2.3.2"
|
||||
tera = "1.8.0"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
sanitize-filename = "0.3"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
tempfile = "3.3.0"
|
||||
minifier = "0.2.2"
|
||||
lettre = { version = "0.10.0-beta.2", default-features = false, features = ["smtp-transport", "tokio1-rustls-tls", "hostname", "r2d2", "builder"] }
|
||||
derive_more = "0.99.17"
|
||||
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM rust:latest
|
||||
|
||||
LABEL Valentin SORLIN
|
||||
|
||||
USER root
|
||||
|
||||
ENV ROCKET_ADDRESS=0.0.0.0
|
||||
ENV ROCKET_PORT=80
|
||||
|
||||
WORKDIR /usr/src/myapp
|
||||
|
||||
RUN apt update
|
||||
|
||||
RUN rustup default nightly
|
||||
RUN cargo install cargo-watch
|
||||
RUN cargo install diesel_cli --no-default-features --features postgres
|
||||
|
||||
CMD ["cargo", "watch", "-x", "run", "-i", "/usr/src/myapp/tmp", "-i", "/usr/src/myapp/staticbuild", "--why"]
|
||||
71
NORME.md
Normal file
71
NORME.md
Normal file
@@ -0,0 +1,71 @@
|
||||
Nommage
|
||||
=====================
|
||||
Les variable
|
||||
--------------------
|
||||
Les variables doivent être en minuscule en [snake_case](https://www.theserverside.com/definition/Snake-case) et sans chiffre et si possible refléter le type de variable
|
||||
|
||||
exemple correct:
|
||||
```
|
||||
let nb_chat = 5; // c'est une qte alors nb
|
||||
let chats = Vec<Chat>::new(); // c'est un tableau alors "s"
|
||||
let is_chat = True; // C'est un booléen du coup is
|
||||
```
|
||||
exemple incorrect:
|
||||
```
|
||||
let 1chien = 1; // incorrect a cause du chiffre
|
||||
let mechantChien = Chien::new(); // incorrect car CamelCase
|
||||
let IS_CHIEN = False;// incorrect car majuscule
|
||||
let nb_chien = Chien::new(); //incorrect la variable s'appel nb et ne représente pas le type de variable qui est ici un instance de Chien
|
||||
```
|
||||
Les classes
|
||||
=====================
|
||||
les classe/struct doivent commencé par une majuscule et être en un mots
|
||||
exemple correct:
|
||||
```
|
||||
struct User {}
|
||||
struct Project {}
|
||||
```
|
||||
exemple incorrect:
|
||||
```
|
||||
struct user{} // incorrect a cause de la majuscule
|
||||
struct ProjectNuageDePoints // incorrect car en plusieurs mots
|
||||
```
|
||||
Structure de code
|
||||
------------------
|
||||
Les variable doivent êtres autant que possible en début de fonction
|
||||
```
|
||||
fct main() {
|
||||
let variable_correct = 22;
|
||||
println("{}", variable_correct);
|
||||
let variable_incorrect = 35; // incorrect car non déclarré avant première action
|
||||
}
|
||||
```
|
||||
Les variable doivent être type si le type c'est pas défini directement.
|
||||
```
|
||||
let tata = String::new("une chaine de caractère"); // pas besoin de spécifier le type car on voit direct que c'est une String
|
||||
let toto:String; // ici on défini le type de la varible car elle n'est pas assigné directement
|
||||
|
||||
toto = "une chaine de caractère mais plus tard";
|
||||
```
|
||||
Pour les block le premier crochet toi être sur le même ligne que l'action(if, for, struct, ...)
|
||||
```
|
||||
if toto == True {
|
||||
//correct
|
||||
}
|
||||
if toto == True
|
||||
{
|
||||
//incorrect
|
||||
}
|
||||
if toto = True
|
||||
//incorrect
|
||||
```
|
||||
Autre
|
||||
---------------
|
||||
- Le nom des fonction doivent décrire que quel fait.
|
||||
- Si un block de code revient souvent on créer une fonction.
|
||||
- La longeur des ligne doivent rentrer dans l'ecrans.
|
||||
- La hauteur des fonction doivent rentrer dans l'ecrans.
|
||||
- Les variables dise a quoi elles servent(pas de toto) sauf pour i et j dans les boucle d'intération.
|
||||
- On ne saute pas des ligne inutilement. Un saut de ligne doit séparée 2 fonctionnement ex: déclaration des variable "\n" utilisation de celle si
|
||||
- On ne garde les commentaires de code que si il a vocation a être décommenté.
|
||||
- Les commentaires peuvent être intéressant si le code être trop complexe a lire (ex: un calcul mathématique) mais il n'est pas obligatoire il faut pensé a réécrire le code pour le rendre plus simple plutôt que de faire des truc complexe avec un commentaire.
|
||||
8
README.md
Normal file
8
README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
#migrate table
|
||||
`./diesel.sh migration run`
|
||||
|
||||
#Create new table
|
||||
`./diesel.sh migration generate example_name`
|
||||
|
||||
#revert migration
|
||||
`./diesel.sh migration revert`
|
||||
1
diesel.sh
Executable file
1
diesel.sh
Executable file
@@ -0,0 +1 @@
|
||||
sudo docker-compose run dr_who_website diesel $@
|
||||
5
diesel.toml
Normal file
5
diesel.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
||||
71
docker-compose.yml
Normal file
71
docker-compose.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
version: "3.3"
|
||||
|
||||
services:
|
||||
dr_who_website:
|
||||
build: "."
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- .:/usr/src/myapp
|
||||
environment:
|
||||
- HOST=dr_who_website.fr
|
||||
- PORT=80
|
||||
- DATABASE_URL=postgresql://admin:tempPassword1234@dr_who_website-db/db
|
||||
- MODE_INSTALL=DEV # DEV, PROD
|
||||
- RUST_BACKTRACE=1
|
||||
- TMPDIR=/usr/src/myapp/tmp
|
||||
- GOOGLE_CLIENT_ID=723424966880-015kn0qncavlgbj4j3k1ggs3arn7tdkd.apps.googleusercontent.com
|
||||
- GOOGLE_CLIENT_SECRET=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
|
||||
depends_on:
|
||||
- dr_who_website-db
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.services.dr_who_website.loadbalancer.server.port=80
|
||||
- traefik.http.routers.dr_who_website-http.entrypoints=http
|
||||
- traefik.http.routers.dr_who_website-http.rule=Host(`dr_who_website.sorlinv.fr`)
|
||||
- traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
|
||||
- traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
|
||||
- traefik.http.routers.dr_who_website-http.middlewares=https-redirect@docker
|
||||
- traefik.http.routers.dr_who_website-https.entrypoints=https
|
||||
- traefik.http.routers.dr_who_website-https.rule=Host(`dr_who_website.sorlinv.fr`)
|
||||
- traefik.http.routers.dr_who_website-https.tls=true
|
||||
- traefik.http.routers.dr_who_website-https.tls.certresolver=letsencrypt
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
dr_who_website-db:
|
||||
image: postgres:latest
|
||||
environment:
|
||||
- POSTGRES_DB=db
|
||||
- POSTGRES_USER=admin
|
||||
- POSTGRES_PASSWORD=tempPassword1234
|
||||
volumes:
|
||||
- ./persistence-db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
dr_who_website-adminer:
|
||||
image: adminer
|
||||
ports:
|
||||
- 8080:8080
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.services.dr_who_website-adminer.loadbalancer.server.port=80
|
||||
- traefik.http.routers.dr_who_website-adminer-http.entrypoints=http
|
||||
- traefik.http.routers.dr_who_website-adminer-http.rule=Host(`dr_who_websiteadmin.sorlinv.fr`)
|
||||
- traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
|
||||
- traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
|
||||
- traefik.http.routers.dr_who_website-adminer-http.middlewares=https-redirect@docker
|
||||
- traefik.http.routers.dr_who_website-adminer-https.entrypoints=https
|
||||
- traefik.http.routers.dr_who_website-adminer-https.rule=Host(`dr_who_websiteadmin.sorlinv.fr`)
|
||||
- traefik.http.routers.dr_who_website-adminer-https.tls=true
|
||||
- traefik.http.routers.dr_who_website-adminer-https.tls.certresolver=letsencrypt
|
||||
depends_on:
|
||||
- dr_who_website-db
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
# external:
|
||||
# name: web_traefik
|
||||
26
k-9_url
Normal file
26
k-9_url
Normal file
@@ -0,0 +1,26 @@
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e1-regeneration/54d111ea69702d0707150800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e2-liberation/54d1122969702d04c9700800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e3-the-korven/54d1124369702d04ca100800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e4-the-bounty-hunter/54d1127e69702d04c9740800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e5-sirens-of-ceres/54d1129669702d07071c0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e6-fear-itself/54d113a069702d04c9830800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e7-the-fall-of-the-house-of-gryffen/54d1142f69702d04c98b0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e8-jaws-of-orthrus/54d1144169702d07072f0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e9-dream-eaters/54d1145769702d04c98d0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e10-curse-of-anubis/54d1146969702d04c9910800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e11-oroborus/54d1147c69702d04ca2c0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e12-alien-avatar/54d1148e69702d04ca2f0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e13-aeolian/54d114ac69702d0707380800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e14-the-last-oak-tree/54d114c069702d04ca390800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e15-black-hunger/54d114e769702d04c99a0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e16-the-cambridge-spy/54d1151269702d04ca3e0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e17-lost-library-of-ukko/54d1152469702d04c99f0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e18-mutant-copper/54d1153769702d0707420800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e19-the-custodians/54d1154969702d04c9a40800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e20-taphony-and-the-time-loop/54d1155c69702d04ca450800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e21-robot-gladiators/54d1157069702d0707460800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e22-mind-snap/54d1158469702d0707490800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e23-angel-of-the-north/54d115a669702d07074c0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e24-the-last-precinct/54d115bb69702d04c9ac0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e25-hound-of-the-korven/54d115cf69702d04ca4a0800
|
||||
https://www.shoutfactorytv.com/k-9/k-9-s1-e26-the-eclipse-of-the-korven/54d115e969702d04c9ae0800
|
||||
1
migrations/2022-07-20-084320_create_users/down.sql
Normal file
1
migrations/2022-07-20-084320_create_users/down.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE users;
|
||||
13
migrations/2022-07-20-084320_create_users/up.sql
Normal file
13
migrations/2022-07-20-084320_create_users/up.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
create table users(
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
salt VARCHAR(255) NOT NULL,
|
||||
image VARCHAR(255) NOT NULL DEFAULT '/static/img/default_user.svg',
|
||||
email VARCHAR(255) NOT NULL DEFAULT '',
|
||||
google VARCHAR(255) NOT NULL DEFAULT '',
|
||||
email_valid VARCHAR(255) NOT NULL DEFAULT '',
|
||||
email_valid VARCHAR(255) NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
insert into users(username, password, salt) values ('adminkey', '', '');
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
pye57==0.4.1
|
||||
cube2sphere==0.2.1
|
||||
opencv-python==4.6.0.66
|
||||
sympy==1.11.1
|
||||
py360convert==0.1.0
|
||||
Pillow==9.3.0
|
||||
518
rust-analyzer.json
Normal file
518
rust-analyzer.json
Normal file
@@ -0,0 +1,518 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"description": "Setting of https://github.com/rust-analyzer/rust-analyzer",
|
||||
"properties": {
|
||||
"rust-analyzer.cargoRunner": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
],
|
||||
"default": null,
|
||||
"description": "Custom cargo runner extension ID."
|
||||
},
|
||||
"rust-analyzer.runnableEnv": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mask": {
|
||||
"type": "string",
|
||||
"description": "Runnable name mask"
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"description": "Variables in form of { \"key\": \"value\"}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Variables in form of { \"key\": \"value\"}"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"markdownDescription": "Environment variables passed to the runnable launched using `Test` or `Debug` lens or `rust-analyzer.run` command."
|
||||
},
|
||||
"rust-analyzer.inlayHints.enable": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether to show inlay hints."
|
||||
},
|
||||
"rust-analyzer.updates.channel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"stable",
|
||||
"nightly"
|
||||
],
|
||||
"default": "stable",
|
||||
"markdownEnumDescriptions": [
|
||||
"`stable` updates are shipped weekly, they don't contain cutting-edge features from VSCode proposed APIs but have less bugs in general.",
|
||||
"`nightly` updates are shipped daily (extension updates automatically by downloading artifacts directly from GitHub), they contain cutting-edge features and latest bug fixes. These releases help us get your feedback very quickly and speed up rust-analyzer development **drastically**."
|
||||
],
|
||||
"markdownDescription": "Choose `nightly` updates to get the latest features and bug fixes every day. While `stable` releases occur weekly and don't contain cutting-edge features from VSCode proposed APIs."
|
||||
},
|
||||
"rust-analyzer.updates.askBeforeDownload": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether to ask for permission before downloading any files from the Internet."
|
||||
},
|
||||
"rust-analyzer.server.path": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
],
|
||||
"default": null,
|
||||
"markdownDescription": "Path to rust-analyzer executable (points to bundled binary by default). If this is set, then `#rust-analyzer.updates.channel#` setting is not used"
|
||||
},
|
||||
"rust-analyzer.server.extraEnv": {
|
||||
"type": [
|
||||
"null",
|
||||
"object"
|
||||
],
|
||||
"default": null,
|
||||
"markdownDescription": "Extra environment variables that will be passed to the rust-analyzer executable. Useful for passing e.g. `RA_LOG` for debugging."
|
||||
},
|
||||
"rust-analyzer.trace.server": {
|
||||
"type": "string",
|
||||
"scope": "window",
|
||||
"enum": [
|
||||
"off",
|
||||
"messages",
|
||||
"verbose"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"No traces",
|
||||
"Error only",
|
||||
"Full log"
|
||||
],
|
||||
"default": "off",
|
||||
"description": "Trace requests to the rust-analyzer (this is usually overly verbose and not recommended for regular users)."
|
||||
},
|
||||
"rust-analyzer.trace.extension": {
|
||||
"description": "Enable logging of VS Code extensions itself.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"rust-analyzer.debug.engine": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"auto",
|
||||
"vadimcn.vscode-lldb",
|
||||
"ms-vscode.cpptools"
|
||||
],
|
||||
"default": "auto",
|
||||
"description": "Preferred debug engine.",
|
||||
"markdownEnumDescriptions": [
|
||||
"First try to use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), if it's not installed try to use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools).",
|
||||
"Use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)",
|
||||
"Use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.debug.sourceFileMap": {
|
||||
"type": "object",
|
||||
"description": "Optional source file mappings passed to the debug engine.",
|
||||
"default": {
|
||||
"/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.debug.openDebugPane": {
|
||||
"markdownDescription": "Whether to open up the `Debug Panel` on debugging start.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"rust-analyzer.debug.engineSettings": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"markdownDescription": "Optional settings passed to the debug engine. Example: `{ \"lldb\": { \"terminal\":\"external\"} }`"
|
||||
},
|
||||
"rust-analyzer.assist.importMergeBehavior": {
|
||||
"markdownDescription": "The strategy to use when inserting new imports or merging imports.",
|
||||
"default": "full",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"full",
|
||||
"last"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"No merging",
|
||||
"Merge all layers of the import trees",
|
||||
"Only merge the last layer of the import trees"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.assist.importPrefix": {
|
||||
"markdownDescription": "The path structure for newly inserted paths to use.",
|
||||
"default": "plain",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"plain",
|
||||
"by_self",
|
||||
"by_crate"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
|
||||
"Prefix all import paths with `self` if they don't begin with `self`, `super`, `crate` or a crate name.",
|
||||
"Force import paths to be absolute by always starting them with `crate` or the crate name they refer to."
|
||||
]
|
||||
},
|
||||
"rust-analyzer.assist.importGroup": {
|
||||
"markdownDescription": "Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.callInfo.full": {
|
||||
"markdownDescription": "Show function name and docs in parameter hints.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.cargo.autoreload": {
|
||||
"markdownDescription": "Automatically refresh project info via `cargo metadata` on\n`Cargo.toml` changes.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.cargo.allFeatures": {
|
||||
"markdownDescription": "Activate all available features (`--all-features`).",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.cargo.features": {
|
||||
"markdownDescription": "List of features to activate.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.cargo.runBuildScripts": {
|
||||
"markdownDescription": "Run build scripts (`build.rs`) for more precise code analysis.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.cargo.noDefaultFeatures": {
|
||||
"markdownDescription": "Do not activate the `default` feature.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.cargo.target": {
|
||||
"markdownDescription": "Compilation target (target triple).",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.cargo.noSysroot": {
|
||||
"markdownDescription": "Internal config for debugging, disables loading of sysroot crates.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.checkOnSave.enable": {
|
||||
"markdownDescription": "Run specified `cargo check` command for diagnostics on save.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.checkOnSave.allFeatures": {
|
||||
"markdownDescription": "Check with all features (`--all-features`).\nDefaults to `#rust-analyzer.cargo.allFeatures#`.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"boolean"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.checkOnSave.allTargets": {
|
||||
"markdownDescription": "Check all targets and tests (`--all-targets`).",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.checkOnSave.command": {
|
||||
"markdownDescription": "Cargo command to use for `cargo check`.",
|
||||
"default": "check",
|
||||
"type": "string"
|
||||
},
|
||||
"rust-analyzer.checkOnSave.noDefaultFeatures": {
|
||||
"markdownDescription": "Do not activate the `default` feature.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"boolean"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.checkOnSave.target": {
|
||||
"markdownDescription": "Check for a specific target. Defaults to\n`#rust-analyzer.cargo.target#`.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.checkOnSave.extraArgs": {
|
||||
"markdownDescription": "Extra arguments for `cargo check`.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.checkOnSave.features": {
|
||||
"markdownDescription": "List of features to activate. Defaults to\n`#rust-analyzer.cargo.features#`.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"array"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.checkOnSave.overrideCommand": {
|
||||
"markdownDescription": "Advanced option, fully override the command rust-analyzer uses for\nchecking. The command should include `--message-format=json` or\nsimilar option.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"array"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.completion.addCallArgumentSnippets": {
|
||||
"markdownDescription": "Whether to add argument snippets when completing functions.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.completion.addCallParenthesis": {
|
||||
"markdownDescription": "Whether to add parenthesis when completing functions.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.completion.postfix.enable": {
|
||||
"markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.completion.autoimport.enable": {
|
||||
"markdownDescription": "Toggles the additional completions that automatically add imports when completed.\nNote that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.diagnostics.enable": {
|
||||
"markdownDescription": "Whether to show native rust-analyzer diagnostics.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.diagnostics.enableExperimental": {
|
||||
"markdownDescription": "Whether to show experimental rust-analyzer diagnostics that might\nhave more false positives than usual.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.diagnostics.disabled": {
|
||||
"markdownDescription": "List of rust-analyzer diagnostics to disable.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"rust-analyzer.diagnostics.warningsAsHint": {
|
||||
"markdownDescription": "List of warnings that should be displayed with info severity.\n\nThe warnings will be indicated by a blue squiggly underline in code\nand a blue icon in the `Problems Panel`.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.diagnostics.warningsAsInfo": {
|
||||
"markdownDescription": "List of warnings that should be displayed with hint severity.\n\nThe warnings will be indicated by faded text or three dots in code\nand will not show up in the `Problems Panel`.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.files.watcher": {
|
||||
"markdownDescription": "Controls file watching implementation.",
|
||||
"default": "client",
|
||||
"type": "string"
|
||||
},
|
||||
"rust-analyzer.files.excludeDirs": {
|
||||
"markdownDescription": "These directories will be ignored by rust-analyzer.",
|
||||
"default": ["**/medias", "**/tmp"],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.hoverActions.debug": {
|
||||
"markdownDescription": "Whether to show `Debug` action. Only applies when\n`#rust-analyzer.hoverActions.enable#` is set.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.hoverActions.enable": {
|
||||
"markdownDescription": "Whether to show HoverActions in Rust files.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.hoverActions.gotoTypeDef": {
|
||||
"markdownDescription": "Whether to show `Go to Type Definition` action. Only applies when\n`#rust-analyzer.hoverActions.enable#` is set.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.hoverActions.implementations": {
|
||||
"markdownDescription": "Whether to show `Implementations` action. Only applies when\n`#rust-analyzer.hoverActions.enable#` is set.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.hoverActions.run": {
|
||||
"markdownDescription": "Whether to show `Run` action. Only applies when\n`#rust-analyzer.hoverActions.enable#` is set.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.hoverActions.linksInHover": {
|
||||
"markdownDescription": "Use markdown syntax for links in hover.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.inlayHints.chainingHints": {
|
||||
"markdownDescription": "Whether to show inlay type hints for method chains.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.inlayHints.maxLength": {
|
||||
"markdownDescription": "Maximum length for inlay hints. Default is unlimited.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"integer"
|
||||
],
|
||||
"minimum": 0
|
||||
},
|
||||
"rust-analyzer.inlayHints.parameterHints": {
|
||||
"markdownDescription": "Whether to show function parameter name inlay hints at the call\nsite.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.inlayHints.typeHints": {
|
||||
"markdownDescription": "Whether to show inlay type hints for variables.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.lens.debug": {
|
||||
"markdownDescription": "Whether to show `Debug` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.lens.enable": {
|
||||
"markdownDescription": "Whether to show CodeLens in Rust files.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.lens.implementations": {
|
||||
"markdownDescription": "Whether to show `Implementations` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.lens.run": {
|
||||
"markdownDescription": "Whether to show `Run` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.lens.methodReferences": {
|
||||
"markdownDescription": "Whether to show `Method References` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.lens.references": {
|
||||
"markdownDescription": "Whether to show `References` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.linkedProjects": {
|
||||
"markdownDescription": "Disable project auto-discovery in favor of explicitly specified set\nof projects.\n\nElements must be paths pointing to `Cargo.toml`,\n`rust-project.json`, or JSON objects in `rust-project.json` format.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": [
|
||||
"string",
|
||||
"object"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rust-analyzer.lruCapacity": {
|
||||
"markdownDescription": "Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"integer"
|
||||
],
|
||||
"minimum": 0
|
||||
},
|
||||
"rust-analyzer.notifications.cargoTomlNotFound": {
|
||||
"markdownDescription": "Whether to show `can't find Cargo.toml` error message.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.procMacro.enable": {
|
||||
"markdownDescription": "Enable support for procedural macros, implies `#rust-analyzer.cargo.runBuildScripts#`.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.procMacro.server": {
|
||||
"markdownDescription": "Internal config, path to proc-macro server executable (typically,\nthis is rust-analyzer itself, but we override this in tests).",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.runnables.overrideCargo": {
|
||||
"markdownDescription": "Command to be executed instead of 'cargo' for runnables.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.runnables.cargoExtraArgs": {
|
||||
"markdownDescription": "Additional arguments to be passed to cargo for runnables such as\ntests or binaries. For example, it may be `--release`.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.rustcSource": {
|
||||
"markdownDescription": "Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private\nprojects, or \"discover\" to try to automatically find it.\n\nAny project which uses rust-analyzer with the rustcPrivate\ncrates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.\n\nThis option is not reloaded automatically; you must restart rust-analyzer for it to take effect.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.rustfmt.extraArgs": {
|
||||
"markdownDescription": "Additional arguments to `rustfmt`.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.rustfmt.overrideCommand": {
|
||||
"markdownDescription": "Advanced option, fully override the command rust-analyzer uses for\nformatting.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"array"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
src/main.rs
Normal file
160
src/main.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
#![allow(unused)]
|
||||
use actix::{Actor, StreamHandler};
|
||||
use actix_files::{Files, NamedFile};
|
||||
use actix_form_data::{Error, Field, Value};
|
||||
use actix_identity::Identity;
|
||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{
|
||||
cookie::Cookie, get, http, middleware, post, web, web::Data, web::Form, web::Path as Pathweb,
|
||||
App, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||
};
|
||||
use actix_web_actors::ws;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::{fs::*, default};
|
||||
use std::path::*;
|
||||
use std::process::Command;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
use diesel::prelude::*;
|
||||
|
||||
pub mod schema;
|
||||
use schema::users;
|
||||
pub mod models;
|
||||
use models::{User, Project, Entity};
|
||||
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
mod user_controller;
|
||||
|
||||
mod response;
|
||||
mod util;
|
||||
use util::*;
|
||||
// use response::MyError;
|
||||
|
||||
use futures_util::TryStreamExt as _;
|
||||
use std::io::Write;
|
||||
use tempfile::tempfile;
|
||||
use uuid::Uuid;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, prelude::*, BufReader};
|
||||
|
||||
struct Ws {}
|
||||
|
||||
impl Actor for Ws {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {}
|
||||
}
|
||||
|
||||
/// Handler for ws::Message message
|
||||
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
|
||||
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
|
||||
match msg {
|
||||
Ok(ws::Message::Ping(msg)) => {
|
||||
println!("msg: {:?}", msg);
|
||||
ctx.pong(&msg)
|
||||
}
|
||||
Ok(ws::Message::Text(text)) => {
|
||||
// println!("text: {:?}", text);
|
||||
// ctx.text(text);
|
||||
let str = &text.to_string();
|
||||
if str == &"Coucou le serveur !".to_string() {
|
||||
// println!("text: {:?}", str);
|
||||
// println!("user: {:?}", User::find(1).unwrap());
|
||||
return ctx.text(text);
|
||||
}
|
||||
ctx.text(text)
|
||||
}
|
||||
Ok(ws::Message::Binary(bin)) => {
|
||||
println!("bin: {:?}", bin);
|
||||
ctx.binary(bin)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/ws")]
|
||||
async fn websocket(req: HttpRequest, stream: web::Payload) -> impl Responder {
|
||||
let resp = ws::start(Ws {}, &req, stream);
|
||||
// println!("{:#?}", &ws);
|
||||
resp
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index(tmpl: Data<Tera>, id: Identity) -> impl Responder {
|
||||
let mut context = Context::new();
|
||||
let result_user = get_user_session(&id);
|
||||
if !result_user.is_err() {
|
||||
let user_session = result_user.unwrap();
|
||||
context.insert("user", &user_session);
|
||||
}
|
||||
let projects = Project::find_by_public(true).unwrap();
|
||||
let mut projects_json = Vec::<serde_json::Value>::new();
|
||||
for project in projects {
|
||||
projects_json.push(project.to_json());
|
||||
}
|
||||
context.insert("projects_json",&projects_json);
|
||||
response::template(tmpl, "index.html.twig", &context)
|
||||
}
|
||||
|
||||
#[get("/home")]
|
||||
async fn home(tmpl: Data<Tera>, id: Identity) -> impl Responder {
|
||||
let mut context = Context::new();
|
||||
response::template(tmpl, "home.html.twig", &context)
|
||||
}
|
||||
|
||||
async fn not_found(req: HttpRequest, tmpl: Data<Tera>, id: Identity) -> impl Responder {
|
||||
let context = Context::new();
|
||||
response::just_404(tmpl)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(|| {
|
||||
let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
|
||||
let mut folder_static = "static/";
|
||||
App::new()
|
||||
.app_data(Data::new(tera))
|
||||
.wrap(IdentityService::new(
|
||||
CookieIdentityPolicy::new(&[0; 32])
|
||||
.name("dr_who_website")
|
||||
.secure(true)
|
||||
.max_age_secs(60 * 60 * 24 * 7), // 1week
|
||||
))
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(index)
|
||||
.service(user_controller::login)
|
||||
.service(user_controller::post_login)
|
||||
.service(user_controller::post_auth)
|
||||
.service(user_controller::post_auth_user)
|
||||
.service(user_controller::register)
|
||||
.service(user_controller::post_register)
|
||||
.service(user_controller::logout)
|
||||
.service(user_controller::register_google)
|
||||
.service(user_controller::post_register_google)
|
||||
.service(user_controller::user_validate)
|
||||
.service(user_controller::forgotpassword)
|
||||
.service(user_controller::post_forgotpassword)
|
||||
.service(user_controller::change_password)
|
||||
.service(user_controller::post_change_password)
|
||||
.service(user_controller::profile)
|
||||
.service(user_controller::profile_post)
|
||||
.service(user_controller::delete)
|
||||
.service(user_controller::user_deleted)
|
||||
.service(websocket)
|
||||
.service(home)
|
||||
.service(Files::new("/static", folder_static))
|
||||
.default_service(web::route().to(not_found))
|
||||
})
|
||||
.workers(8)
|
||||
.bind(("0.0.0.0", 80))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
776
src/models.rs
Normal file
776
src/models.rs
Normal file
@@ -0,0 +1,776 @@
|
||||
use super::util::*;
|
||||
use crate::schema::*;
|
||||
use diesel::pg::*;
|
||||
use diesel::prelude::*;
|
||||
use rand::prelude::*;
|
||||
use serde_json::{json, Serializer, Value};
|
||||
use std::path::Path;
|
||||
use std::time::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn db() -> PgConnection {
|
||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url))
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable, Identifiable, Insertable, serde::Serialize)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub salt: String,
|
||||
pub image: String,
|
||||
pub email: String,
|
||||
pub google: String,
|
||||
pub email_valid: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
// pub fn create(key: String) -> Result<Self, String> {
|
||||
// let uuid = Uuid::new_v4().to_string();
|
||||
// println!("{}", uuid);
|
||||
// let result = diesel::insert_into(users::table)
|
||||
// .values((
|
||||
// users::dsl::username.eq(key),
|
||||
// users::dsl::password.eq("".to_string()),
|
||||
// users::dsl::salt.eq("".to_string()),
|
||||
// users::dsl::image.eq("/static/img/default_user.svg".to_string()),
|
||||
// users::dsl::email_valid.eq(uuid),
|
||||
// users::dsl::email.eq("".to_string()),
|
||||
// users::dsl::google.eq("".to_string()),
|
||||
// ))
|
||||
// .get_result(&db());
|
||||
// if !result.is_err() {
|
||||
// let user_session = result.unwrap();
|
||||
// return Ok(user_session);
|
||||
// }
|
||||
// Err(format!("User not created"))
|
||||
// }
|
||||
|
||||
pub fn create(username: String, email: String, pass_not_hashed: String) -> Result<Self, String> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let y: f64 = rng.gen();
|
||||
let salt = sha256::digest(y.to_string());
|
||||
|
||||
let pass_hashed = sha256::digest(format!("{}{}", &salt, &pass_not_hashed));
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let result = diesel::insert_into(users::table)
|
||||
.values((
|
||||
users::dsl::username.eq(username),
|
||||
users::dsl::password.eq(pass_hashed),
|
||||
users::dsl::salt.eq(salt),
|
||||
users::dsl::image.eq("/static/img/default_user.svg".to_string()),
|
||||
users::dsl::email_valid.eq(uuid),
|
||||
users::dsl::email.eq(email),
|
||||
users::dsl::google.eq("".to_string()),
|
||||
))
|
||||
.get_result(&db());
|
||||
if !result.is_err() {
|
||||
let user_session = result.unwrap();
|
||||
return Ok(user_session);
|
||||
}
|
||||
match result.err() {
|
||||
Some(x) => {
|
||||
if format!("{}", x).contains("email") {
|
||||
return Err("Un compte est déjà associé à cet Email".to_string());
|
||||
} else if format!("{}", x).contains("username") {
|
||||
return Err("Ce nom d'utilisateur est déja utilisé".to_string());
|
||||
}
|
||||
return Err("Could not create user".to_string());
|
||||
},
|
||||
None => return Err("Could not create user".to_string())
|
||||
}
|
||||
Err("Could not create user".to_string())
|
||||
}
|
||||
|
||||
pub fn find_all() -> Result<Vec<Self>, String> {
|
||||
let result = users::dsl::users.get_results::<User>(&db());
|
||||
if !result.is_err() {
|
||||
let users = result.unwrap();
|
||||
return Ok(users);
|
||||
}
|
||||
Err(format!("Users not found"))
|
||||
}
|
||||
|
||||
pub fn find(id: i32) -> Result<Self, String> {
|
||||
let result = users::dsl::users
|
||||
.filter(users::dsl::id.eq(id))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let user_session = result.unwrap();
|
||||
return Ok(user_session);
|
||||
}
|
||||
Err(format!("User {} not found", id))
|
||||
}
|
||||
|
||||
pub fn update(self) -> Result<Self, String> {
|
||||
let filter = users::dsl::users.filter(users::dsl::id.eq(self.id));
|
||||
let result = diesel::update(filter)
|
||||
.set((
|
||||
users::dsl::username.eq(self.username),
|
||||
users::dsl::password.eq(self.password),
|
||||
users::dsl::salt.eq(self.salt),
|
||||
users::dsl::image.eq(self.image),
|
||||
users::dsl::email.eq(self.email),
|
||||
users::dsl::email_valid.eq(self.email_valid),
|
||||
users::dsl::google.eq(self.google),
|
||||
))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let user_session = result.unwrap();
|
||||
return Ok(user_session);
|
||||
}
|
||||
Err(format!("User {} not update", self.id))
|
||||
}
|
||||
|
||||
pub fn find_by_username(username: &str) -> Result<Self, String> {
|
||||
let result = users::dsl::users
|
||||
.filter(users::dsl::username.eq(&username))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let user = result.unwrap();
|
||||
return Ok(user);
|
||||
}
|
||||
Err(format!("User {} not find", &username))
|
||||
}
|
||||
|
||||
pub fn find_by_changepass(changepass: &str) -> Result<Self, String> {
|
||||
let result = users::dsl::users
|
||||
.filter(users::dsl::password.eq(&changepass))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let user = result.unwrap();
|
||||
return Ok(user);
|
||||
}
|
||||
Err(format!("User {} not find", &changepass))
|
||||
}
|
||||
|
||||
pub fn find_by_email(email: &str) -> Result<Self, String> {
|
||||
let result = users::dsl::users
|
||||
.filter(users::dsl::email.eq(&email))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let user = result.unwrap();
|
||||
return Ok(user);
|
||||
}
|
||||
Err(format!("User {} not find", &email))
|
||||
}
|
||||
|
||||
pub fn find_by_key(key: &str) -> Result<Self, String> {
|
||||
let result = users::dsl::users
|
||||
.filter(users::dsl::username.eq(&key))
|
||||
.filter(users::dsl::password.eq(""))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let user = result.unwrap();
|
||||
return Ok(user);
|
||||
}
|
||||
Err(format!("User {} not find", &key))
|
||||
}
|
||||
|
||||
pub fn find_by_google(google: &str) -> Result<Self, String> {
|
||||
let result = users::dsl::users
|
||||
.filter(users::dsl::google.eq(&google))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let user = result.unwrap();
|
||||
return Ok(user);
|
||||
}
|
||||
Err(format!("User by google {} not find", &google))
|
||||
}
|
||||
|
||||
pub fn find_by_email_valid(email_valid: &str) -> Result<Self, String> {
|
||||
let result = users::dsl::users
|
||||
.filter(users::dsl::email_valid.eq(&email_valid))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let user = result.unwrap();
|
||||
return Ok(user);
|
||||
}
|
||||
Err(format!("User by email_valid {} not find", &email_valid))
|
||||
}
|
||||
|
||||
pub fn generate_salt(&mut self) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let y: f64 = rng.gen(); // generates a float between 0 and 1
|
||||
self.salt = sha256::digest(y.to_string())
|
||||
}
|
||||
|
||||
pub fn get_projects(&self) -> Result<Vec<Project>, String> {
|
||||
let result = projects::dsl::projects
|
||||
.filter(projects::dsl::creator_id.eq(&self.id))
|
||||
.get_results::<Project>(&db());
|
||||
if !result.is_err() {
|
||||
let projects = result.unwrap();
|
||||
return Ok(projects);
|
||||
}
|
||||
Err(format!("Project {} not fetched", self.id))
|
||||
}
|
||||
|
||||
pub fn get_project_accesss(&self) -> Result<Vec<ProjectAccess>, String> {
|
||||
let result = project_accesss::dsl::project_accesss
|
||||
.filter(project_accesss::dsl::user_id.eq(&self.id))
|
||||
.get_results::<ProjectAccess>(&db());
|
||||
if !result.is_err() {
|
||||
let project_accesss = result.unwrap();
|
||||
return Ok(project_accesss);
|
||||
}
|
||||
Err(format!("ProjectAccesss {} not fetched", self.id))
|
||||
}
|
||||
|
||||
pub fn delete(self) -> Result<String, String> {
|
||||
let id = self.id.clone();
|
||||
let name = self.username.clone();
|
||||
let result_projects = self.get_projects();
|
||||
let projects = result_projects.unwrap();
|
||||
for project in projects {
|
||||
project.delete();
|
||||
}
|
||||
let filter_user = users::dsl::users.filter(users::dsl::id.eq(&id));
|
||||
let result_del_user = diesel::delete(filter_user)
|
||||
.returning(users::dsl::username)
|
||||
.get_result::<String>(&db());
|
||||
if result_del_user.is_err() {
|
||||
return Err(format!("User cannot be deleted in db()"));
|
||||
}
|
||||
return Ok("User was deleted".to_string());
|
||||
}
|
||||
|
||||
pub fn to_json(self) -> Value {
|
||||
let mut user_value = json!(self);
|
||||
let project_accesss = self.get_project_accesss().unwrap();
|
||||
let mut project_accesss_json = Vec::new();
|
||||
for project_access in project_accesss {
|
||||
project_accesss_json.push(project_access.to_json());
|
||||
}
|
||||
user_value["project_accesss"] = json!(project_accesss_json);
|
||||
user_value["projects"] = json!(self.get_projects().unwrap());
|
||||
user_value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable, Identifiable, Insertable, serde::Serialize)]
|
||||
pub struct Project {
|
||||
pub id: i32,
|
||||
pub creator_id: i32,
|
||||
pub uuid: String,
|
||||
pub name: String,
|
||||
pub password: String,
|
||||
pub time_limit: SystemTime,
|
||||
pub premium: i32,
|
||||
pub is_public: bool,
|
||||
pub image: String,
|
||||
pub origin_filename: String,
|
||||
pub description: String,
|
||||
pub data: String,
|
||||
}
|
||||
impl Project {
|
||||
const FREE: i32 = 0;
|
||||
const PAID: i32 = 1;
|
||||
|
||||
pub fn create(
|
||||
name: String,
|
||||
origin_filename: String,
|
||||
password: String,
|
||||
user: &User,
|
||||
) -> Result<Self, String> {
|
||||
let result = diesel::insert_into(projects::table)
|
||||
.values((
|
||||
projects::dsl::origin_filename.eq(origin_filename),
|
||||
projects::dsl::creator_id.eq(user.id),
|
||||
projects::dsl::uuid.eq(Uuid::new_v4().to_string()),
|
||||
projects::dsl::name.eq(name),
|
||||
projects::dsl::password.eq(password),
|
||||
projects::dsl::time_limit
|
||||
.eq(SystemTime::now() + Duration::new(60 * 60 * 24 * 5, 0)), // calcul pour 5 jour en seconde
|
||||
projects::dsl::premium.eq(Project::FREE),
|
||||
projects::dsl::is_public.eq(true),
|
||||
projects::dsl::description.eq("".to_string()),
|
||||
projects::dsl::data.eq("{}".to_string()),
|
||||
))
|
||||
.get_result(&db());
|
||||
if !result.is_err() {
|
||||
let project = result.unwrap();
|
||||
return Ok(project);
|
||||
}
|
||||
Err(format!("Project not created"))
|
||||
}
|
||||
|
||||
pub fn find(id: i32) -> Result<Self, String> {
|
||||
let result = projects::dsl::projects
|
||||
.filter(projects::dsl::id.eq(id))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let project = result.unwrap();
|
||||
return Ok(project);
|
||||
}
|
||||
Err(format!("User {} not found", id))
|
||||
}
|
||||
|
||||
pub fn find_by_uuid(uuid: &str) -> Result<Self, String> {
|
||||
let result = projects::dsl::projects
|
||||
.filter(projects::dsl::uuid.eq(&uuid))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let project = result.unwrap();
|
||||
return Ok(project);
|
||||
}
|
||||
Err(format!("Project {} not find", &uuid))
|
||||
}
|
||||
|
||||
pub fn find_by_public(is_public: bool) -> Result<Vec<Self>, String> {
|
||||
let result = projects::dsl::projects
|
||||
.filter(projects::dsl::is_public.eq(&is_public))
|
||||
.get_results::<Project>(&db());
|
||||
if !result.is_err() {
|
||||
let projects = result.unwrap();
|
||||
return Ok(projects);
|
||||
}
|
||||
Err(format!("User {} not found", &is_public))
|
||||
}
|
||||
|
||||
pub fn update(self) -> Result<Self, String> {
|
||||
let filter = projects::dsl::projects.filter(projects::dsl::id.eq(self.id));
|
||||
let result = diesel::update(filter)
|
||||
.set((
|
||||
projects::dsl::image.eq(self.image),
|
||||
projects::dsl::origin_filename.eq(self.origin_filename),
|
||||
projects::dsl::creator_id.eq(self.creator_id),
|
||||
projects::dsl::uuid.eq(self.uuid),
|
||||
projects::dsl::name.eq(self.name),
|
||||
projects::dsl::password.eq(self.password),
|
||||
projects::dsl::time_limit.eq(self.time_limit),
|
||||
projects::dsl::premium.eq(self.premium),
|
||||
projects::dsl::is_public.eq(self.is_public),
|
||||
projects::dsl::data.eq(self.data),
|
||||
projects::dsl::description.eq(self.description),
|
||||
))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let project = result.unwrap();
|
||||
return Ok(project);
|
||||
}
|
||||
Err(format!("Project {} not update", self.id))
|
||||
}
|
||||
|
||||
pub fn get_entitys(&self) -> Result<Vec<Entity>, String> {
|
||||
let result = entitys::dsl::entitys
|
||||
.filter(entitys::dsl::project_id.eq(&self.id))
|
||||
.get_results::<Entity>(&db());
|
||||
if !result.is_err() {
|
||||
let entitys = result.unwrap();
|
||||
return Ok(entitys);
|
||||
}
|
||||
Err(format!("Project entitys {} not fetched", self.id))
|
||||
}
|
||||
|
||||
pub fn get_creator(&self) -> Result<User, String> {
|
||||
User::find(self.creator_id)
|
||||
}
|
||||
|
||||
pub fn get_project_accesss(&self) -> Result<Vec<ProjectAccess>, String> {
|
||||
let result = project_accesss::dsl::project_accesss
|
||||
.filter(project_accesss::dsl::project_id.eq(&self.id))
|
||||
.get_results::<ProjectAccess>(&db());
|
||||
if !result.is_err() {
|
||||
let project_accesss = result.unwrap();
|
||||
return Ok(project_accesss);
|
||||
}
|
||||
Err(format!("ProjectAccesss {} not fetched", self.id))
|
||||
}
|
||||
|
||||
pub fn delete(self) -> Result<String, String> {
|
||||
let result_file = std::fs::remove_dir_all(Path::new("medias").join(self.uuid));
|
||||
let filter_entity = entitys::dsl::entitys.filter(entitys::dsl::project_id.eq(&self.id));
|
||||
let result_del_entity = diesel::delete(filter_entity)
|
||||
.returning(entitys::dsl::name)
|
||||
.get_results::<String>(&db());
|
||||
if result_del_entity.is_err() {
|
||||
return Err(format!("Project delete could not remove entity in db()"));
|
||||
}
|
||||
let filter_project = projects::dsl::projects.filter(projects::dsl::id.eq(&self.id));
|
||||
let result_del_project = diesel::delete(filter_project)
|
||||
.returning(projects::dsl::name)
|
||||
.get_result::<String>(&db());
|
||||
if result_del_project.is_err() {
|
||||
return Err(format!("Project delete could not remove project in db()"));
|
||||
}
|
||||
return Ok("Project was deleted".to_string());
|
||||
}
|
||||
|
||||
pub fn get_logs(&self) -> String {
|
||||
let path = Path::new("./medias")
|
||||
.join(self.uuid.clone())
|
||||
.join("cmd_logs.txt");
|
||||
if path.exists() {
|
||||
return String::from_utf8_lossy(&std::fs::read(&path).unwrap()).to_string();
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
const CONVERTION: i32 = 0;
|
||||
const COMPLETE: i32 = 1;
|
||||
const CRASHED: i32 = 2;
|
||||
|
||||
pub fn get_status(&self) -> i32 {
|
||||
let path = Path::new("./medias")
|
||||
.join(self.uuid.clone())
|
||||
.join("cmd_logs.txt");
|
||||
if path.exists() {
|
||||
let metadata = std::fs::metadata(path).unwrap();
|
||||
let modified_time = metadata.modified().unwrap().elapsed().unwrap().as_secs();
|
||||
// println!("{} > 10", modified_time);
|
||||
if modified_time > 300 {
|
||||
return Project::CRASHED;
|
||||
}
|
||||
return Project::CONVERTION;
|
||||
}
|
||||
Project::COMPLETE
|
||||
}
|
||||
|
||||
pub const ACCESSREAD: i32 = 0;
|
||||
pub const ACCESSWRITE: i32 = 1;
|
||||
|
||||
pub fn test_access(&self, result_user: &Result<User, String>, access_type: i32) -> bool {
|
||||
if access_type == Project::ACCESSREAD && self.is_public {
|
||||
return true;
|
||||
}
|
||||
else if access_type == Project::ACCESSREAD && !result_user.is_err() {
|
||||
let user = result_user.as_ref().unwrap();
|
||||
if user.id == self.creator_id {
|
||||
return true;
|
||||
}
|
||||
let result_pa = ProjectAccess::find(user.id,self.id);
|
||||
if(!result_pa.is_err()){
|
||||
let pa = result_pa.unwrap();
|
||||
if(pa.access_type == Project::ACCESSREAD || pa.access_type == Project::ACCESSWRITE){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if access_type == Project::ACCESSWRITE && !result_user.is_err() {
|
||||
let user = result_user.as_ref().unwrap();
|
||||
if user.id == self.creator_id {
|
||||
return true;
|
||||
}
|
||||
let result_pa = ProjectAccess::find(user.id,self.id);
|
||||
if(!result_pa.is_err()){
|
||||
let pa = result_pa.unwrap();
|
||||
if(pa.access_type == Project::ACCESSWRITE){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> Value {
|
||||
let mut project_value = json!(self);
|
||||
project_value["entitys"] = json!(self.get_entitys().unwrap());
|
||||
project_value["creator"] = json!(self.get_creator().unwrap());
|
||||
project_value["cmd_logs"] = json!(self.get_logs());
|
||||
project_value["status"] = json!(self.get_status());
|
||||
project_value["data"] = self.get_data();
|
||||
let project_accesss = self.get_project_accesss().unwrap();
|
||||
let mut project_accesss_json = Vec::new();
|
||||
for project_access in project_accesss {
|
||||
project_accesss_json.push(project_access.to_json());
|
||||
}
|
||||
project_value["project_accesss"] = json!(project_accesss_json);
|
||||
project_value
|
||||
}
|
||||
|
||||
pub fn duplicate(&self) -> Result<Self, String> {
|
||||
let user = User::find(self.creator_id)?;
|
||||
let mut project = Project::create(self.name.clone()+"_copy", self.origin_filename.clone(), self.password.clone(), &user)?;
|
||||
project.description = self.description.clone();
|
||||
project.is_public = self.is_public.clone();
|
||||
project = project.update().unwrap();
|
||||
let copy_result = copy_dir_all(Path::new("./medias").join(self.uuid.clone()), Path::new("./medias").join(project.uuid.clone()));
|
||||
if copy_result.is_err() {
|
||||
return Err(format!("You cannot copy {} in {}",self.uuid,project.uuid));
|
||||
}
|
||||
copy_result.unwrap();
|
||||
let entitys = self.get_entitys()?;
|
||||
for e in entitys {
|
||||
let entity = e.duplicate(&project);
|
||||
}
|
||||
return Ok(project);
|
||||
}
|
||||
|
||||
pub fn set_data(&mut self, object: &Value) {
|
||||
self.data = object.to_string();
|
||||
}
|
||||
|
||||
pub fn get_data(&self) -> Value {
|
||||
serde_json::from_str(self.data.as_str()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable, Identifiable, Insertable, serde::Serialize)]
|
||||
pub struct Entity {
|
||||
pub id: i32,
|
||||
pub project_id: i32,
|
||||
pub name: String,
|
||||
pub type_entity: i32,
|
||||
pub data: String,
|
||||
pub parent_id: Option<i32>,
|
||||
}
|
||||
impl Entity {
|
||||
pub const POTREE: i32 = 0;
|
||||
pub const MODELE: i32 = 1;
|
||||
pub const IMAGE360: i32 = 2;
|
||||
pub const POTREEITEM: i32 = 3;
|
||||
pub const GROUP: i32 = 3;
|
||||
|
||||
pub fn create(name: String, type_entity: i32, project: &Project) -> Result<Self, String> {
|
||||
let result = diesel::insert_into(entitys::table)
|
||||
.values((
|
||||
entitys::dsl::project_id.eq(project.id),
|
||||
entitys::dsl::name.eq(name),
|
||||
entitys::dsl::type_entity.eq(type_entity),
|
||||
entitys::dsl::data.eq(json!({}).to_string()),
|
||||
entitys::dsl::parent_id.eq(None::<i32>),
|
||||
))
|
||||
.get_result(&db());
|
||||
if !result.is_err() {
|
||||
let entity = result.unwrap();
|
||||
return Ok(entity);
|
||||
}
|
||||
Err(format!("Entity not created"))
|
||||
}
|
||||
|
||||
pub fn duplicate(&self , project: &Project) -> Result<Self, String> {
|
||||
let result = diesel::insert_into(entitys::table)
|
||||
.values((
|
||||
entitys::dsl::project_id.eq(project.id),
|
||||
entitys::dsl::name.eq(self.name.clone()),
|
||||
entitys::dsl::type_entity.eq(self.type_entity),
|
||||
entitys::dsl::data.eq(self.data.clone()),
|
||||
entitys::dsl::parent_id.eq(None::<i32>),
|
||||
))
|
||||
.get_result(&db());
|
||||
if !result.is_err() {
|
||||
let entity = result.unwrap();
|
||||
return Ok(entity);
|
||||
}
|
||||
Err(format!("Entity not created"))
|
||||
}
|
||||
|
||||
pub fn find(id: i32) -> Result<Self, String> {
|
||||
let result = entitys::dsl::entitys
|
||||
.filter(entitys::dsl::id.eq(id))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let entity = result.unwrap();
|
||||
return Ok(entity);
|
||||
}
|
||||
Err(format!("User {} not found", id))
|
||||
}
|
||||
|
||||
pub fn update(self) -> Result<Self, String> {
|
||||
let filter = entitys::dsl::entitys.filter(entitys::dsl::id.eq(self.id));
|
||||
let result = diesel::update(filter)
|
||||
.set((
|
||||
entitys::dsl::project_id.eq(self.project_id),
|
||||
entitys::dsl::name.eq(self.name),
|
||||
entitys::dsl::type_entity.eq(self.type_entity),
|
||||
entitys::dsl::data.eq(self.data),
|
||||
entitys::dsl::parent_id.eq(self.parent_id),
|
||||
))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let entity = result.unwrap();
|
||||
return Ok(entity);
|
||||
}
|
||||
Err(format!("Project {} not update", self.id))
|
||||
}
|
||||
|
||||
pub fn set_data(&mut self, object: &Value) {
|
||||
self.data = object.to_string();
|
||||
}
|
||||
|
||||
pub fn get_data(&self) -> Value {
|
||||
serde_json::from_str(self.data.as_str()).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_project(&self) -> Result<Project, String> {
|
||||
let result = projects::dsl::projects
|
||||
.filter(projects::dsl::id.eq(self.project_id))
|
||||
.get_result::<Project>(&db());
|
||||
if !result.is_err() {
|
||||
let project = result.unwrap();
|
||||
return Ok(project);
|
||||
}
|
||||
Err(format!("User {} not found", self.project_id))
|
||||
}
|
||||
|
||||
pub fn find_by_name_and_project(name: String, project_id: i32) -> Vec<Entity> {
|
||||
let result = entitys::dsl::entitys
|
||||
.filter(entitys::dsl::name.eq(&name))
|
||||
.filter(entitys::dsl::project_id.eq(&project_id))
|
||||
.get_results::<Entity>(&db());
|
||||
if !result.is_err() {
|
||||
let entitys = result.unwrap();
|
||||
return entitys;
|
||||
}
|
||||
Vec::<Entity>::new()
|
||||
}
|
||||
|
||||
pub fn delete(self) -> Result<String, String> {
|
||||
let id = self.id.clone();
|
||||
let name = self.name.clone();
|
||||
let result_project = self.get_project();
|
||||
if result_project.is_err() {
|
||||
return Err(format!("Entity delete could not find project"));
|
||||
}
|
||||
let project = result_project.unwrap();
|
||||
let entity_with_same_file = Entity::find_by_name_and_project(self.name, self.project_id);
|
||||
|
||||
if self.type_entity != Entity::POTREEITEM && entity_with_same_file.len() <= 1 {
|
||||
let entity_path = Path::new("medias").join(project.uuid).join(&name);
|
||||
if entity_path.exists() && entity_path.is_dir() {
|
||||
let result_file = std::fs::remove_dir_all(entity_path);
|
||||
if result_file.is_err() {
|
||||
return Err(format!("Entity delete could not remove folder"));
|
||||
}
|
||||
} else if entity_path.exists() {
|
||||
let result_file = std::fs::remove_file(entity_path);
|
||||
if result_file.is_err() {
|
||||
return Err(format!("Entity delete could not remove file"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let filter_entity = entitys::dsl::entitys.filter(entitys::dsl::id.eq(&id));
|
||||
let result_del_entity = diesel::delete(filter_entity)
|
||||
.returning(entitys::dsl::name)
|
||||
.get_result::<String>(&db());
|
||||
if result_del_entity.is_err() {
|
||||
return Err(format!("Entity delete could not remove project in db()"));
|
||||
}
|
||||
return Ok("Entity was deleted".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable, Identifiable, Insertable, serde::Serialize)]
|
||||
pub struct ProjectAccess {
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
pub project_id: i32,
|
||||
pub access_type: i32,
|
||||
}
|
||||
impl ProjectAccess {
|
||||
|
||||
pub fn create(
|
||||
user: &User,
|
||||
project: &Project,
|
||||
access: i32,
|
||||
) -> Result<Self, String> {
|
||||
let project_access_result = ProjectAccess::find(user.id, project.id);
|
||||
if !project_access_result.is_err(){
|
||||
let mut project_access = project_access_result.unwrap();
|
||||
project_access.access_type = access;
|
||||
project_access = project_access.update().unwrap();
|
||||
return Ok(project_access);
|
||||
}
|
||||
if ProjectAccess::find(user.id, project.id).is_err() {
|
||||
let result = diesel::insert_into(project_accesss::table)
|
||||
.values((
|
||||
project_accesss::dsl::user_id.eq(user.id),
|
||||
project_accesss::dsl::project_id.eq(project.id),
|
||||
project_accesss::dsl::access_type.eq(access),
|
||||
))
|
||||
.get_result(&db());
|
||||
let pa_project = project_accesss::dsl::project_accesss
|
||||
.filter(project_accesss::dsl::project_id.eq(project.id))
|
||||
.get_results::<ProjectAccess>(&db())
|
||||
.unwrap();
|
||||
if !result.is_err() {
|
||||
let project_access = result.unwrap();
|
||||
return Ok(project_access);
|
||||
}
|
||||
}
|
||||
Err(format!("Project_Access not created"))
|
||||
}
|
||||
|
||||
pub fn get_user(&self) -> Result<User, String> {
|
||||
let result = users::dsl::users
|
||||
.filter(users::dsl::id.eq(&self.user_id))
|
||||
.get_result::<User>(&db());
|
||||
if !result.is_err() {
|
||||
let user = result.unwrap();
|
||||
return Ok(user);
|
||||
}
|
||||
Err(format!("User {} not found", self.user_id))
|
||||
}
|
||||
|
||||
pub fn get_project(&self) -> Result<Project, String> {
|
||||
let result = projects::dsl::projects
|
||||
.filter(projects::dsl::id.eq(&self.project_id))
|
||||
.get_result::<Project>(&db());
|
||||
if !result.is_err() {
|
||||
let project = result.unwrap();
|
||||
return Ok(project);
|
||||
}
|
||||
Err(format!("Project {} not found", self.project_id))
|
||||
}
|
||||
|
||||
pub fn find(user_id: i32, project_id: i32) -> Result<Self, String> {
|
||||
let result = project_accesss::dsl::project_accesss
|
||||
.filter(project_accesss::dsl::user_id.eq(user_id))
|
||||
.filter(project_accesss::dsl::project_id.eq(project_id))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let project_access = result.unwrap();
|
||||
return Ok(project_access);
|
||||
}
|
||||
Err(format!("Project Access with project_id{} and user_id{} not found", project_id, user_id))
|
||||
}
|
||||
|
||||
pub fn find_id(id: i32) -> Result<Self, String> {
|
||||
let result = project_accesss::dsl::project_accesss
|
||||
.filter(project_accesss::dsl::id.eq(id))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let project_access = result.unwrap();
|
||||
return Ok(project_access);
|
||||
}
|
||||
Err(format!("ProjectAccess {} not found", id))
|
||||
}
|
||||
|
||||
pub fn update(self) -> Result<Self, String> {
|
||||
let filter = project_accesss::dsl::project_accesss.filter(project_accesss::dsl::id.eq(self.id));
|
||||
let result = diesel::update(filter)
|
||||
.set((
|
||||
project_accesss::dsl::user_id.eq(self.user_id),
|
||||
project_accesss::dsl::project_id.eq(self.project_id),
|
||||
project_accesss::dsl::access_type.eq(self.access_type),
|
||||
))
|
||||
.get_result::<Self>(&db());
|
||||
if !result.is_err() {
|
||||
let project_access = result.unwrap();
|
||||
return Ok(project_access);
|
||||
}
|
||||
Err(format!("ProjectAccess {} not update", self.id))
|
||||
}
|
||||
|
||||
pub fn delete(self) -> Result<String, String> {
|
||||
let id = self.id.clone();
|
||||
let filter_pa = project_accesss::dsl::project_accesss.filter(project_accesss::dsl::id.eq(&id));
|
||||
let result_del_pa = diesel::delete(filter_pa)
|
||||
.returning(project_accesss::dsl::id)
|
||||
.get_result::<i32>(&db());
|
||||
if result_del_pa.is_err() {
|
||||
return Err(format!("ProjectAccess cannot be deleted in db()"));
|
||||
}
|
||||
return Ok("ProjectAccess was deleted".to_string());
|
||||
}
|
||||
|
||||
pub fn to_json(self) -> Value {
|
||||
let mut project_access_value = json!(self);
|
||||
project_access_value["user"] = json!(self.get_user().unwrap());
|
||||
project_access_value["project"] = json!(self.get_project().unwrap());
|
||||
project_access_value
|
||||
}
|
||||
}
|
||||
79
src/response.rs
Normal file
79
src/response.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use actix_files::{Files, NamedFile};
|
||||
use actix_identity::Identity;
|
||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_web::{
|
||||
cookie::Cookie, get, http, middleware, post, web, web::Data, web::Form, App, HttpRequest,
|
||||
HttpResponse, HttpServer, Responder,
|
||||
http::{header::ContentType, StatusCode}, error::ResponseError
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use derive_more::{Display, Error};
|
||||
|
||||
// #[derive(Debug, Display)]
|
||||
// pub struct MyError {
|
||||
// tmpl: Tera,
|
||||
// message_erreur: String,
|
||||
// }
|
||||
// impl MyError {
|
||||
// pub fn new(_tmpl: Tera, message: &String) -> Self {
|
||||
// MyError {
|
||||
// tmpl: _tmpl,
|
||||
// message_erreur: message.to_string(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// impl ResponseError for MyError {
|
||||
|
||||
// fn error_response(&self) -> HttpResponse {
|
||||
// let context = Context::new();
|
||||
// context.insert("message_erreur", &self.message_erreur);
|
||||
// let result_tmpl = self.tmpl.render("catch/500.html.twig", &context);
|
||||
// let body = "internal error".to_string();
|
||||
// if !result_tmpl.is_err() {
|
||||
// body = result_tmpl.unwrap();
|
||||
// }
|
||||
// HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
// .insert_header(ContentType::html())
|
||||
// .body(body)
|
||||
// }
|
||||
|
||||
// // fn status_code(&self) -> StatusCode {
|
||||
// // match *self {
|
||||
// // MyError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
// // MyError::BadClientData => StatusCode::BAD_REQUEST,
|
||||
// // MyError::Timeout => StatusCode::GATEWAY_TIMEOUT,
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
|
||||
pub fn json(obj: Value) -> HttpResponse {
|
||||
HttpResponse::Ok().insert_header(ContentType::json()).body(obj.to_string())
|
||||
}
|
||||
|
||||
pub fn text(s: String) -> HttpResponse {
|
||||
HttpResponse::Ok().body(s)
|
||||
}
|
||||
|
||||
pub fn template(tmpl: Data<Tera>, name: &'static str, context: &Context) -> HttpResponse {
|
||||
HttpResponse::Ok().body(tmpl.render(name, context).unwrap())
|
||||
}
|
||||
|
||||
pub fn redirect(uri: &str) -> HttpResponse {
|
||||
HttpResponse::Found()
|
||||
.append_header((http::header::LOCATION, uri))
|
||||
.finish()
|
||||
}
|
||||
|
||||
pub fn just_404(tmpl: Data<Tera>) -> HttpResponse {
|
||||
let context = Context::new();
|
||||
HttpResponse::NotFound().body(tmpl.render("catch/404.html.twig", &context).unwrap())
|
||||
// HttpResponse::NotFound().finish()
|
||||
}
|
||||
|
||||
pub fn file(req: HttpRequest, path: PathBuf) -> HttpResponse {
|
||||
NamedFile::open(path).unwrap().into_response(&req)
|
||||
}
|
||||
63
src/schema.rs
Normal file
63
src/schema.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
entitys (id) {
|
||||
id -> Int4,
|
||||
project_id -> Int4,
|
||||
name -> Varchar,
|
||||
type_entity -> Int4,
|
||||
data -> Text,
|
||||
parent_id -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
project_accesss (id) {
|
||||
id -> Int4,
|
||||
user_id -> Int4,
|
||||
project_id -> Int4,
|
||||
access_type -> Int4,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
projects (id) {
|
||||
id -> Int4,
|
||||
creator_id -> Int4,
|
||||
uuid -> Varchar,
|
||||
name -> Varchar,
|
||||
password -> Varchar,
|
||||
time_limit -> Timestamp,
|
||||
premium -> Int4,
|
||||
is_public -> Bool,
|
||||
image -> Varchar,
|
||||
origin_filename -> Varchar,
|
||||
description -> Text,
|
||||
data -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
users (id) {
|
||||
id -> Int4,
|
||||
username -> Varchar,
|
||||
password -> Varchar,
|
||||
salt -> Varchar,
|
||||
image -> Varchar,
|
||||
email -> Varchar,
|
||||
google -> Varchar,
|
||||
email_valid -> Varchar,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(entitys -> projects (project_id));
|
||||
diesel::joinable!(project_accesss -> projects (project_id));
|
||||
diesel::joinable!(project_accesss -> users (user_id));
|
||||
diesel::joinable!(projects -> users (creator_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
entitys,
|
||||
project_accesss,
|
||||
projects,
|
||||
users,
|
||||
);
|
||||
BIN
src/soft/ffmpeg
Executable file
BIN
src/soft/ffmpeg
Executable file
Binary file not shown.
BIN
src/soft/ffprobe
Executable file
BIN
src/soft/ffprobe
Executable file
Binary file not shown.
507
src/user_controller.rs
Normal file
507
src/user_controller.rs
Normal file
@@ -0,0 +1,507 @@
|
||||
#![allow(unused)]
|
||||
use actix_files::{Files, NamedFile};
|
||||
use actix_form_data::{Error, Field};
|
||||
use actix_identity::Identity;
|
||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{
|
||||
cookie::Cookie, get, http, middleware, post, web, web::Data, web::Form, web::Path as Pathweb,
|
||||
App, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||
};
|
||||
|
||||
use lettre::transport::smtp::commands::Mail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::format;
|
||||
use std::{fs::*, string};
|
||||
use std::path::*;
|
||||
use std::sync::mpsc::Sender;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
use super::models::User;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
use serde_json::{json, Serializer, Value};
|
||||
|
||||
use super::response;
|
||||
use super::util::*;
|
||||
|
||||
use futures_util::TryStreamExt as _;
|
||||
use std::io::Write;
|
||||
use tempfile::tempfile;
|
||||
use uuid::Uuid;
|
||||
|
||||
// use oauth2::basic::BasicClient;
|
||||
// use oauth2::reqwest::async_http_client;
|
||||
// use oauth2::{
|
||||
// AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl,
|
||||
// Scope, TokenResponse, TokenUrl,
|
||||
// };
|
||||
//
|
||||
// use jsonwebtoken::errors::ErrorKind;
|
||||
// use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
||||
|
||||
use jsonwebtoken_google::*;
|
||||
|
||||
// #[get("/user_admin")]
|
||||
// pub async fn user_admin(req: HttpRequest, tmpl: Data<Tera>, id: Identity) -> impl Responder {
|
||||
// let result_user = get_user_session(&id);
|
||||
// if result_user.is_err() {
|
||||
// return response::redirect("/login");
|
||||
// }
|
||||
// let user_session = result_user.unwrap();
|
||||
// if user_session.username != "admin" {
|
||||
// return response::redirect("/");
|
||||
// }
|
||||
// let result = User::find_all();
|
||||
// if result.is_err() {
|
||||
// return response::redirect("/login");
|
||||
// }
|
||||
// let users = result.unwrap();
|
||||
// let uri = format!(
|
||||
// "{}://{}",
|
||||
// req.connection_info().scheme(),
|
||||
// req.connection_info().host()
|
||||
// );
|
||||
// let mut context = Context::new();
|
||||
// context.insert("user", &user_session);
|
||||
// context.insert("users", &users);
|
||||
// context.insert("uri", uri.as_str());
|
||||
// response::template(tmpl, "user/user_admin.html.twig", &context)
|
||||
// }
|
||||
//
|
||||
// #[get("/add_user")]
|
||||
// pub async fn add_user(id: Identity) -> impl Responder {
|
||||
// let result_user = get_user_session(&id);
|
||||
// if result_user.is_err() {
|
||||
// return response::redirect("/login");
|
||||
// }
|
||||
// let user_session = result_user.unwrap();
|
||||
// let result = User::create(generate_key());
|
||||
// if result.is_err() {
|
||||
// return response::redirect("/user_admin");
|
||||
// }
|
||||
// response::redirect("/user_admin")
|
||||
// }pl, "user/login.html.twig", &Context::new())
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Params {
|
||||
message_login: String,
|
||||
}
|
||||
|
||||
|
||||
#[get("/login")]
|
||||
pub async fn login(req: HttpRequest, tmpl: Data<Tera>) -> impl Responder {
|
||||
let mut context = Context::new();
|
||||
let params_result = web::Query::<Params>::from_query(req.query_string());
|
||||
if !params_result.is_err() {
|
||||
let params = params_result.unwrap();
|
||||
context.insert("message_login", ¶ms.message_login);
|
||||
}
|
||||
response::template(tmpl, "user/login.html.twig", &context)
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginForm {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
#[post("/login")]
|
||||
pub async fn post_login(
|
||||
req: HttpRequest,
|
||||
tmpl: Data<Tera>,
|
||||
id: Identity,
|
||||
form_login: web::Form<LoginForm>,
|
||||
) -> impl Responder {
|
||||
let mut context_err = Context::new();
|
||||
context_err.insert("message_login", "Wrong login or password !");
|
||||
let mut result = User::find_by_username(&form_login.username);
|
||||
if result.is_err() {
|
||||
result = User::find_by_email(&form_login.username);
|
||||
if result.is_err() {
|
||||
return response::template(tmpl, "user/login.html.twig", &context_err);
|
||||
}
|
||||
}
|
||||
let user = result.unwrap();
|
||||
let password = sha256::digest(format!("{}{}", user.salt, form_login.password));
|
||||
if user.password != password {
|
||||
return response::template(tmpl, "user/login.html.twig", &context_err);
|
||||
}
|
||||
id.remember(user.id.to_string());
|
||||
return response::redirect("/");
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TokenClaims {
|
||||
pub email: String,
|
||||
pub aud: String,
|
||||
pub iss: String,
|
||||
pub exp: u64,
|
||||
pub picture: String,
|
||||
pub sub: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginGoogleForm {
|
||||
credential: String,
|
||||
g_csrf_token: String,
|
||||
}
|
||||
#[post("/auth")]
|
||||
pub async fn post_auth(
|
||||
req: HttpRequest,
|
||||
tmpl: Data<Tera>,
|
||||
id: Identity,
|
||||
form_login: web::Form<LoginGoogleForm>,
|
||||
) -> impl Responder {
|
||||
let parser = Parser::new(env!("GOOGLE_CLIENT_ID"));
|
||||
let claims = parser
|
||||
.parse::<TokenClaims>(&form_login.credential)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user_result = User::find_by_google(&claims.sub);
|
||||
if user_result.is_err() {
|
||||
let key = generate_key();
|
||||
let result_user = User::create(claims.email.clone(), claims.email.clone(), "".to_string());
|
||||
if result_user.is_err() {
|
||||
return response::redirect("/login?message_login=L'email du compte google est utilisé mais pas synchronisé");
|
||||
}
|
||||
let mut user = result_user.unwrap();
|
||||
user.username = key.clone();
|
||||
user.password = "".to_string();
|
||||
user.salt = "".to_string();
|
||||
user.google = claims.sub.clone();
|
||||
user.update().unwrap();
|
||||
return response::redirect(format!("/register_google/{}", key).as_str());
|
||||
}
|
||||
let user = user_result.unwrap();
|
||||
if user.password == "" {
|
||||
return response::redirect(format!("/register_google/{}", user.username).as_str());
|
||||
}
|
||||
id.remember(user.id.to_string());
|
||||
|
||||
response::redirect("/")
|
||||
}
|
||||
#[post("/auth/{user_id}")]
|
||||
pub async fn post_auth_user(
|
||||
req: HttpRequest,
|
||||
tmpl: Data<Tera>,
|
||||
id: Identity,
|
||||
form_login: web::Form<LoginGoogleForm>,
|
||||
user_id: Pathweb<i32>
|
||||
) -> impl Responder {
|
||||
let user = get_user_session(&id).unwrap();
|
||||
if user.id != user_id.clone() {
|
||||
return response::redirect("/");
|
||||
}
|
||||
let parser = Parser::new(env!("GOOGLE_CLIENT_ID"));
|
||||
let claims = parser
|
||||
.parse::<TokenClaims>(&form_login.credential)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut user = User::find(user_id.clone()).unwrap();
|
||||
user.google = claims.sub.clone();
|
||||
if user.email_valid != "" {
|
||||
user.email = claims.email.clone();
|
||||
user.email_valid = "".to_string();
|
||||
}
|
||||
let user = user.update().unwrap();
|
||||
|
||||
response::redirect(format!("/profile/{}", user_id.clone()).as_str())
|
||||
}
|
||||
|
||||
#[get("/register")]
|
||||
pub async fn register(tmpl: Data<Tera>) -> impl Responder {
|
||||
let mut context = Context::new();
|
||||
response::template(tmpl, "user/register.html.twig", &context)
|
||||
}
|
||||
#[get("/register_google/{key}")]
|
||||
pub async fn register_google(tmpl: Data<Tera>, key: Pathweb<String>) -> impl Responder {
|
||||
let key_str = key.as_str();
|
||||
let result = User::find_by_key(&key_str);
|
||||
if result.is_err() {
|
||||
return response::redirect("/login");
|
||||
}
|
||||
let user = result.unwrap();
|
||||
let mut context = Context::new();
|
||||
context.insert("user_id", &key_str);
|
||||
context.insert("registrable_user", &user);
|
||||
response::template(tmpl, "user/register_google.html.twig", &context)
|
||||
}
|
||||
#[post("/register")]
|
||||
pub async fn post_register(
|
||||
req: HttpRequest,
|
||||
tmpl: Data<Tera>,
|
||||
id: Identity,
|
||||
mut payload: Multipart,
|
||||
) -> impl Responder {
|
||||
let form_data = get_form_data(payload).await;
|
||||
let uuid_image = Uuid::new_v4().to_string();
|
||||
let final_path_local = Path::new("./static/sourcefiles").join(&uuid_image);
|
||||
let final_path = Path::new("/static/sourcefiles").join(&uuid_image);
|
||||
|
||||
if form_data["password"].value != form_data["repassword"].value {
|
||||
let mut context = Context::new();
|
||||
context.insert("message_register", "Les mots de passe sont différents");
|
||||
return response::template(tmpl, "user/register.html.twig", &context);
|
||||
}
|
||||
let mut user_result = User::create(form_data["username"].value.clone(), form_data["email"].value.clone(), form_data["password"].value.clone());
|
||||
if user_result.is_err() {
|
||||
print!("user error");
|
||||
let mut context = Context::new();
|
||||
context.insert("message_register", &user_result.err());
|
||||
return response::template(tmpl, "user/register.html.twig", &context);
|
||||
}
|
||||
let mut user = user_result.unwrap();
|
||||
if Path::new(&form_data["image"].path.clone()).exists() {
|
||||
form_data["image"].clone().save_file(&final_path_local);
|
||||
user.image = "/static/img/default_user.svg".to_string();
|
||||
if final_path_local.exists() {
|
||||
user.image = final_path
|
||||
.to_str()
|
||||
.expect("final_path convertion str err")
|
||||
.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("user", &user);
|
||||
context.insert("uri", &format!("{}://{}", req.connection_info().scheme(), req.connection_info().host()).to_string());
|
||||
envoye_un_mail(&tmpl, "mail/welcome.html.twig", &context, "Bienvenue sur DrWhy", form_data["email"].value.clone().as_str());
|
||||
let result = user.update();
|
||||
if !result.is_err() {
|
||||
let mut user = result.unwrap();
|
||||
id.remember(user.id.to_string());
|
||||
return response::redirect("/");
|
||||
}
|
||||
let mut context = Context::new();
|
||||
context.insert("message_register", "Erreur incconue");
|
||||
response::template(tmpl, "user/register.html.twig", &context)
|
||||
}
|
||||
#[post("/register_google/{key}")]
|
||||
pub async fn post_register_google(
|
||||
tmpl: Data<Tera>,
|
||||
id: Identity,
|
||||
key: Pathweb<String>,
|
||||
mut payload: Multipart,
|
||||
) -> impl Responder {
|
||||
let form_data = get_form_data(payload).await;
|
||||
let key_str = key.as_str();
|
||||
let result = User::find_by_key(key_str);
|
||||
if !result.is_err() {
|
||||
let mut user = result.unwrap();
|
||||
let uuid_image = Uuid::new_v4().to_string();
|
||||
let final_path_local = Path::new("./static/sourcefiles").join(&uuid_image);
|
||||
let final_path = Path::new("/static/sourcefiles").join(&uuid_image);
|
||||
|
||||
if form_data["password"].value != form_data["repassword"].value {
|
||||
let mut context = Context::new();
|
||||
context.insert("user_id", &key_str);
|
||||
context.insert("message_register", "Les mots de passe sont différents");
|
||||
context.insert("registrable_user", &user);
|
||||
return response::template(tmpl, "user/register_google.html.twig", &context);
|
||||
}
|
||||
user.username = form_data["username"].value.clone();
|
||||
user.generate_salt();
|
||||
user.password = sha256::digest(format!("{}{}", &user.salt, &form_data["password"].value));
|
||||
let result = user.update();
|
||||
if !result.is_err() {
|
||||
let mut user = result.unwrap();
|
||||
id.remember(user.id.to_string());
|
||||
return response::redirect("/");
|
||||
}
|
||||
}
|
||||
let mut context = Context::new();
|
||||
context.insert("user_id", &key_str);
|
||||
context.insert("message", "Erreur inconnue");
|
||||
response::template(tmpl, "user/register_google.html.twig", &context)
|
||||
}
|
||||
|
||||
#[get("/logout")]
|
||||
pub async fn logout(req: HttpRequest, id: Identity) -> impl Responder {
|
||||
let redir = req
|
||||
.headers()
|
||||
.get(http::header::REFERER)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
id.forget(); // <- remove identity
|
||||
response::redirect(redir)
|
||||
}
|
||||
|
||||
|
||||
#[get("/user/validate/{uuid}")]
|
||||
pub async fn user_validate(req: HttpRequest, id: Identity, uuid: Pathweb<String>) -> impl Responder {
|
||||
let mut user = User::find_by_email_valid(uuid.into_inner().as_str()).unwrap();
|
||||
user.email_valid = "".to_string();
|
||||
user.update();
|
||||
response::redirect("/")
|
||||
}
|
||||
|
||||
|
||||
#[get("/forgotpassword")]
|
||||
pub async fn forgotpassword(tmpl: Data<Tera>) -> impl Responder {
|
||||
response::template(tmpl, "user/forgotpassword.html.twig", &Context::new())
|
||||
}
|
||||
|
||||
#[post("/forgotpassword")]
|
||||
pub async fn post_forgotpassword(
|
||||
req: HttpRequest,
|
||||
tmpl: Data<Tera>,
|
||||
mut payload: Multipart,
|
||||
) -> impl Responder {
|
||||
let form_data = get_form_data(payload).await;
|
||||
let mut context_mail = Context::new();
|
||||
let email_to = form_data["email"].value.clone();
|
||||
let user_result = User::find_by_email(&email_to);
|
||||
if user_result.is_err() {
|
||||
let mut context_page = Context::new();
|
||||
context_page.insert("message_mail_nok", "L'adresse Email n'existe pas");
|
||||
return response::template(tmpl, "user/forgotpassword.html.twig", &context_page);
|
||||
}
|
||||
let mut user = user_result.unwrap();
|
||||
user.password = Uuid::new_v4().to_string();
|
||||
user.salt = String::new();
|
||||
context_mail.insert("user", &user);
|
||||
user.update().unwrap();
|
||||
context_mail.insert("uri", &format!("{}://{}", req.connection_info().scheme(), req.connection_info().host()).to_string());
|
||||
envoye_un_mail(&tmpl, "mail/forgot_password.html.twig", &context_mail, "Mot de passe oublié", &email_to);
|
||||
let mut context_page = Context::new();
|
||||
context_page.insert("message_password", "Un mail vous a été envoyé");
|
||||
response::template(tmpl, "user/forgotpassword.html.twig", &context_page)
|
||||
}
|
||||
|
||||
#[get("/change_password/{user_changepass}")]
|
||||
pub async fn change_password(tmpl: Data<Tera>, user_changepass: Pathweb<String>) -> impl Responder {
|
||||
let result_user = User::find_by_changepass(&user_changepass);
|
||||
if result_user.is_err() {
|
||||
return response::just_404(tmpl);
|
||||
}
|
||||
let user_session = result_user.unwrap();
|
||||
if user_session.password != user_changepass.into_inner() {}
|
||||
let mut context = Context::new();
|
||||
context.insert("user", &user_session);
|
||||
response::template(tmpl, "user/change_password.html.twig", &context)
|
||||
}
|
||||
|
||||
#[post("/change_password/{user_changepass}")]
|
||||
pub async fn post_change_password(
|
||||
tmpl: Data<Tera>,
|
||||
mut payload: Multipart,
|
||||
user_changepass: Pathweb<String>
|
||||
) -> impl Responder {
|
||||
let mut user = User::find_by_changepass(&user_changepass).unwrap();
|
||||
let form_data = get_form_data(payload).await;
|
||||
if form_data["password"].value != form_data["repassword"].value {
|
||||
let mut context_error = Context::new();
|
||||
user.password = user_changepass.into_inner();
|
||||
context_error.insert("user", &user);
|
||||
context_error.insert("message_error", "Les mots de passe sont différents");
|
||||
return response::template(tmpl, "user/change_password.html.twig", &context_error);
|
||||
}
|
||||
user.generate_salt();
|
||||
user.password = sha256::digest(format!(
|
||||
"{}{}",
|
||||
&user.salt, &form_data["password"].value
|
||||
));
|
||||
user = user.update().unwrap();
|
||||
response::redirect("/login?message_login=Mot de passe modifié")
|
||||
}
|
||||
|
||||
#[get("/profile/{user_id}")]
|
||||
async fn profile(tmpl: Data<Tera>, user_id: Pathweb<i32>, id: Identity) -> impl Responder {
|
||||
let result_user = get_user_session(&id);
|
||||
if result_user.is_err() {
|
||||
return response::redirect("/login");
|
||||
}
|
||||
let user_session = result_user.unwrap();
|
||||
if user_session.id != user_id.into_inner() {}
|
||||
let mut context = Context::new();
|
||||
context.insert("user", &user_session);
|
||||
response::template(tmpl, "user/profile.html.twig", &context)
|
||||
}
|
||||
|
||||
#[post("/profile/{user_id}")]
|
||||
async fn profile_post(
|
||||
req: HttpRequest,
|
||||
tmpl: Data<Tera>,
|
||||
user_id: Pathweb<i32>,
|
||||
id: Identity,
|
||||
mut payload: Multipart,
|
||||
) -> impl Responder {
|
||||
let result_user = get_user_session(&id);
|
||||
if result_user.is_err() {
|
||||
return response::redirect("/login");
|
||||
}
|
||||
let mut user_session = result_user.unwrap();
|
||||
let form_data = get_form_data(payload).await;
|
||||
let user_id = user_id.into_inner();
|
||||
if user_session.id == user_id {
|
||||
let uuid_image = Uuid::new_v4().to_string();
|
||||
let final_path_local = Path::new("./static/sourcefiles").join(&uuid_image);
|
||||
let final_path = Path::new("/static/sourcefiles").join(&uuid_image);
|
||||
form_data["image"].clone().save_file(&final_path_local);
|
||||
if final_path_local.exists() {
|
||||
user_session.image = final_path
|
||||
.to_str()
|
||||
.expect("final_path convertion str err")
|
||||
.to_string();
|
||||
}
|
||||
|
||||
if form_data["password"].value != "" {
|
||||
if form_data["password"].value != form_data["repassword"].value {
|
||||
let mut context = Context::new();
|
||||
context.insert("user", &user_session);
|
||||
context.insert("message_register", "Les mots de passe sont différents");
|
||||
return response::template(tmpl, "user/profile.html.twig", &context);
|
||||
}
|
||||
user_session.generate_salt();
|
||||
user_session.password = sha256::digest(format!(
|
||||
"{}{}",
|
||||
&user_session.salt, &form_data["password"].value
|
||||
));
|
||||
}
|
||||
user_session.username = form_data["username"].value.clone();
|
||||
user_session.email = form_data["email"].value.clone();
|
||||
user_session = user_session.update().unwrap();
|
||||
}
|
||||
let mut context = Context::new();
|
||||
context.insert("message_profile","Votre profil a été mis à jour");
|
||||
context.insert("user", &user_session);
|
||||
response::template(tmpl, "user/profile.html.twig", &context)
|
||||
}
|
||||
|
||||
#[get("/delete/{user_id}")]
|
||||
async fn delete(
|
||||
req: HttpRequest,
|
||||
tmpl: Data<Tera>,
|
||||
id: Identity,
|
||||
user_id: Pathweb<i32>,
|
||||
mut payload: Multipart,
|
||||
) -> impl Responder {
|
||||
let redir = req
|
||||
.headers()
|
||||
.get(http::header::REFERER)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
let result_user_test = get_user_session(&id);
|
||||
if result_user_test.is_err() {
|
||||
return response::redirect(redir);
|
||||
}
|
||||
|
||||
let result_user = User::find(user_id.into_inner());
|
||||
if result_user.is_err() {
|
||||
return response::redirect(redir);
|
||||
}
|
||||
let user = result_user.unwrap();
|
||||
user.delete().unwrap();
|
||||
return response::redirect("/user_deleted");
|
||||
}
|
||||
|
||||
#[get("/user_deleted")]
|
||||
pub async fn user_deleted(tmpl: Data<Tera>) -> impl Responder {
|
||||
let mut context = Context::new();
|
||||
response::template(tmpl, "user/user_deleted.html.twig", &context)
|
||||
}
|
||||
413
src/util.rs
Normal file
413
src/util.rs
Normal file
@@ -0,0 +1,413 @@
|
||||
#![allow(unused)]
|
||||
// #[macro_use]
|
||||
// extern crate diesel;
|
||||
extern crate dotenv;
|
||||
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::prelude::*;
|
||||
use dotenv::dotenv;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{self, Read, Seek, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use super::models::User;
|
||||
use super::schema::users;
|
||||
use actix_identity::Identity;
|
||||
use actix_multipart::Multipart;
|
||||
use futures_util::TryStreamExt as _;
|
||||
use serde_json::{json, Value};
|
||||
use std::fs::File;
|
||||
use std::process::*;
|
||||
use tempfile::NamedTempFile;
|
||||
use uuid::Uuid;
|
||||
use actix_web::web::Data;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::iter::Iterator;
|
||||
// use zip::result::ZipError;
|
||||
// use zip::write::FileOptions;
|
||||
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
// use reqwest::blocking::Client;
|
||||
|
||||
|
||||
extern crate minifier;
|
||||
use minifier::js::minify;
|
||||
|
||||
use tera::{Context, Tera};
|
||||
|
||||
use lettre::{
|
||||
transport::smtp::{
|
||||
authentication::{Credentials, Mechanism},
|
||||
PoolConfig,
|
||||
},
|
||||
message::{header, MultiPart, SinglePart},
|
||||
Message, SmtpTransport, Transport,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct FormData {
|
||||
pub name: String,
|
||||
pub content_type: String,
|
||||
pub value: String,
|
||||
pub filename: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl FormData {
|
||||
pub fn new(_name: String, _content_type: String) -> Self {
|
||||
FormData {
|
||||
name: _name,
|
||||
content_type: _content_type,
|
||||
value: "".to_string(),
|
||||
filename: "".to_string(),
|
||||
path: "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_file<P: AsRef<Path>>(self, path: P) {
|
||||
let content = String::from_utf8_lossy(&std::fs::read(&self.path).unwrap()).to_string();
|
||||
|
||||
if content != "" {
|
||||
std::fs::create_dir_all(&path.as_ref().parent().unwrap()).unwrap();
|
||||
std::fs::rename(self.path, &path.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enum FormDataVec {
|
||||
// FormData(FormData),
|
||||
// FormDataVec(Vec<FormData>),
|
||||
// }
|
||||
|
||||
pub async fn get_form_data(mut payload: Multipart) -> HashMap<String, FormData> {
|
||||
let mut form_datas: HashMap<String, FormData> = HashMap::new();
|
||||
let mut is_finish = false;
|
||||
while !is_finish {
|
||||
let mut field_result = &mut payload.try_next().await;
|
||||
if field_result.is_ok() {
|
||||
if let Some(field) = field_result.as_mut().unwrap() {
|
||||
let mut form_data =
|
||||
FormData::new(field.name().to_string(), field.content_type().to_string());
|
||||
let content_disposition = field.content_disposition();
|
||||
let filename_option = content_disposition.get_filename();
|
||||
if filename_option.is_some() {
|
||||
form_data.filename = filename_option.unwrap().to_string();
|
||||
let mut f = NamedTempFile::new().unwrap();
|
||||
form_data.path = f.into_temp_path().to_str().unwrap().to_string();
|
||||
let mut file = std::fs::File::create(form_data.path.clone()).unwrap();
|
||||
while let Some(chunk) = field.try_next().await.unwrap() {
|
||||
file.write_all(&chunk);
|
||||
}
|
||||
} else {
|
||||
let mut value = String::new();
|
||||
while let Some(chunk) = field.try_next().await.unwrap() {
|
||||
let str_chunk = std::str::from_utf8(&chunk).unwrap();
|
||||
value.push_str(str_chunk);
|
||||
}
|
||||
form_data.value = value;
|
||||
// field.get_result()
|
||||
}
|
||||
form_datas.insert(form_data.name.clone(), form_data);
|
||||
} else {
|
||||
is_finish = true;
|
||||
}
|
||||
} else {
|
||||
is_finish = true;
|
||||
println!("{:?}", "this form is not enctype=multipart/form-data");
|
||||
}
|
||||
}
|
||||
return form_datas;
|
||||
}
|
||||
|
||||
pub fn generate_key() -> String {
|
||||
use rand::Rng;
|
||||
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
|
||||
abcdefghijklmnopqrstuvwxyz\
|
||||
0123456789";
|
||||
const PASSWORD_LEN: usize = 10;
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let password: String = (0..PASSWORD_LEN)
|
||||
.map(|_| {
|
||||
let idx = rng.gen_range(0..CHARSET.len());
|
||||
CHARSET[idx] as char
|
||||
})
|
||||
.collect();
|
||||
return password;
|
||||
}
|
||||
|
||||
pub fn get_user_session(id: &Identity) -> Result<User, String> {
|
||||
if let Some(user_id) = id.identity() {
|
||||
let result = User::find(user_id.parse::<i32>().unwrap());
|
||||
return result;
|
||||
}
|
||||
Err("Not cookie found".to_string())
|
||||
}
|
||||
|
||||
pub async fn convert_video<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||
from: P,
|
||||
to: Q,
|
||||
data: &Value,
|
||||
quality: &'static str,
|
||||
) {
|
||||
let mut buffer: String;
|
||||
let mut to_folder = to.as_ref().parent().unwrap();
|
||||
let stdout_path = to_folder.join(format!(
|
||||
"{}{}",
|
||||
to.as_ref().file_name().unwrap().to_str().unwrap(),
|
||||
"_stdout.txt"
|
||||
));
|
||||
let data_path = to_folder.join(format!(
|
||||
"{}{}",
|
||||
to.as_ref().file_name().unwrap().to_str().unwrap(),
|
||||
"_data.json"
|
||||
));
|
||||
std::fs::write(&data_path, data.to_string());
|
||||
let mut final_name = to.as_ref().as_os_str().to_str().unwrap();
|
||||
// let mut str = final_name.to_string();
|
||||
// str = str.replace(".mp4", format!("{}.mp4", quality).as_str());
|
||||
// final_name = str.as_str();
|
||||
|
||||
println!(
|
||||
"converting {:?} to {:?}",
|
||||
from.as_ref().as_os_str().to_str().unwrap(),
|
||||
&final_name
|
||||
);
|
||||
let mut cmd = Command::new("./src/soft/ffmpeg");
|
||||
cmd.stdout(Stdio::from(File::create(&stdout_path).unwrap()))
|
||||
.stderr(Stdio::null())
|
||||
.stdin(Stdio::null())
|
||||
.args([
|
||||
"-y",
|
||||
"-progress",
|
||||
"-",
|
||||
"-i",
|
||||
from.as_ref().as_os_str().to_str().unwrap(),
|
||||
"-vcodec",
|
||||
"libx264",
|
||||
"-acodec",
|
||||
"aac",
|
||||
"-scodec",
|
||||
"mov_text",
|
||||
"-map",
|
||||
"V",
|
||||
"-map",
|
||||
"a?",
|
||||
"-map",
|
||||
"s?",
|
||||
]);
|
||||
if quality != "copy" {
|
||||
cmd.args(["-s", &quality]);
|
||||
}
|
||||
cmd.arg(final_name);
|
||||
let mut ffmpeg = cmd.spawn().expect("failed to execute child");
|
||||
ffmpeg.wait();
|
||||
std::fs::remove_file(&stdout_path);
|
||||
std::fs::write(&data_path, get_video_data(&to).unwrap().to_string());
|
||||
println!(
|
||||
"converting end {:?} to {:?}",
|
||||
from.as_ref().as_os_str().to_str().unwrap(),
|
||||
to.as_ref().as_os_str().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
pub fn convert_e57_las<P: AsRef<Path>>(path: P) -> Result<String, String> {
|
||||
let stdout_path = path.as_ref().parent().unwrap().join("cmd_logs.txt");
|
||||
let mut cmd = Command::new("wine"); // wine ./src/soft/e572las.exe -v -i \"{e57filepath}\" -o \"{e57filepath}.las\
|
||||
cmd.stdout(Stdio::null())
|
||||
.stderr(Stdio::from(File::create(&stdout_path).unwrap()))
|
||||
.stdin(Stdio::null())
|
||||
.args([
|
||||
"./src/soft/e572las.exe",
|
||||
"-v",
|
||||
"-i",
|
||||
format!("{}", path.as_ref().as_os_str().to_str().unwrap()).as_str(),
|
||||
"-o",
|
||||
format!("{}.las", path.as_ref().as_os_str().to_str().unwrap()).as_str(),
|
||||
]);
|
||||
println!("{:?}", cmd);
|
||||
|
||||
let mut ffmpeg = cmd.spawn().expect("failed to execute wine e572las");
|
||||
ffmpeg.wait();
|
||||
std::fs::remove_file(&stdout_path);
|
||||
// let mut output = cmd.output().expect("failed to execute wine e572las");
|
||||
// let result = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
println!("convert_e57_las");
|
||||
Ok("result".to_string())
|
||||
}
|
||||
|
||||
pub fn convert_las_potree<P: AsRef<Path>>(path: P) -> Result<String, String> {
|
||||
let stdout_path = path.as_ref().parent().unwrap().join("cmd_logs.txt");
|
||||
let mut cmd = Command::new("./src/e57extractor/PotreeConverter"); // wine ./src/soft/e572las.exe -v -i \"{e57filepath}\" -o \"{e57filepath}.las\
|
||||
cmd.stdout(Stdio::from(File::create(&stdout_path).unwrap()))
|
||||
.stderr(Stdio::null())
|
||||
.stdin(Stdio::null())
|
||||
.args([
|
||||
// "./src/e57extractor/PotreeConverter.exe",
|
||||
format!("{}", path.as_ref().as_os_str().to_str().unwrap()).as_str(),
|
||||
]);
|
||||
println!("{:?}", cmd);
|
||||
|
||||
let mut output = cmd.output().expect("failed to execute PotreeConverter");
|
||||
let fs = std::fs::File::create(
|
||||
format!(
|
||||
"{}_converted/sources.json",
|
||||
path.as_ref().as_os_str().to_str().unwrap()
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
let mut ffmpeg = cmd.spawn().expect("failed to execute PotreeConverter");
|
||||
ffmpeg.wait();
|
||||
std::fs::remove_file(&stdout_path);
|
||||
// let mut output = cmd.output().expect("failed to execute PotreeConverter");
|
||||
// let result = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
println!("convert_las_potree");
|
||||
Ok("result".to_string())
|
||||
}
|
||||
|
||||
pub fn convert_e57_images<P: AsRef<Path>>(path: P) -> Result<String, String> {
|
||||
let stdout_path = path.as_ref().parent().unwrap().join("cmd_logs.txt");
|
||||
let mut cmd = Command::new("python3"); // wine ./src/soft/e572las.exe -v -i \"{e57filepath}\" -o \"{e57filepath}.las\
|
||||
cmd.stdout(Stdio::from(File::create(&stdout_path).unwrap()))
|
||||
.stderr(Stdio::from(File::create(&stdout_path).unwrap()))
|
||||
.stdin(Stdio::null())
|
||||
.args([
|
||||
"./src/e57extractor/",
|
||||
format!("{}", path.as_ref().as_os_str().to_str().unwrap()).as_str(),
|
||||
"--only-images",
|
||||
]);
|
||||
println!("{:?}", cmd);
|
||||
|
||||
let mut ffmpeg = cmd.spawn().expect("failed to execute PotreeConverter");
|
||||
ffmpeg.wait();
|
||||
|
||||
let output = String::from_utf8_lossy(&std::fs::read(&stdout_path).unwrap()).to_string();
|
||||
std::fs::remove_file(&stdout_path);
|
||||
if output.contains("File contains no 2D images. Exiting...") || output.contains("E57_ERROR") {
|
||||
println!("No image found");
|
||||
return Err("No image found".to_string());
|
||||
}
|
||||
println!("convert_e57_images");
|
||||
println!("{}", output);
|
||||
Ok("result".to_string())
|
||||
}
|
||||
|
||||
pub fn get_video_data<P: AsRef<Path>>(path: P) -> Result<Value, serde_json::Error> {
|
||||
let mut output = Command::new("./src/soft/ffprobe")
|
||||
.args([
|
||||
"-print_format",
|
||||
"json",
|
||||
"-show_format",
|
||||
"-show_streams",
|
||||
"-show_chapters",
|
||||
path.as_ref().as_os_str().to_str().unwrap(),
|
||||
])
|
||||
.output()
|
||||
.expect("failed to execute ffprobe");
|
||||
let str = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
let result: Result<Value, serde_json::Error> = serde_json::from_str(&str);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn save_file_field(field: &FormData) -> String {
|
||||
let extension = Path::new(&field.filename)
|
||||
.extension()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let final_path_local =
|
||||
Path::new("./static/sourcefiles").join(format!("{}.{}", uuid, extension));
|
||||
let final_path = Path::new("/static/sourcefiles")
|
||||
.join(format!("{}.{}", uuid, extension))
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
std::fs::create_dir_all("./static/sourcefiles");
|
||||
std::fs::rename(&field.path, &final_path_local);
|
||||
|
||||
return final_path;
|
||||
}
|
||||
|
||||
pub fn obfuscation_static() {
|
||||
println!("{}", "Starting obfuscation_static...");
|
||||
let build_dir_path = Path::new("staticbuild");
|
||||
if build_dir_path.exists() {
|
||||
std::fs::remove_dir_all("staticbuild").unwrap();
|
||||
}
|
||||
for entry in WalkDir::new("static") {
|
||||
let path_str = entry.unwrap().path().display().to_string();
|
||||
let path_str_build = path_str.clone().replace("static", "staticbuild");
|
||||
let file_path = Path::new(&path_str);
|
||||
let file_build_path = Path::new(&path_str_build);
|
||||
std::fs::create_dir_all(&file_build_path.parent().unwrap()).unwrap();
|
||||
if file_path.is_file() {
|
||||
let extension_result = file_path.extension();
|
||||
if extension_result.is_none() {
|
||||
std::fs::copy(file_path, file_build_path).unwrap();
|
||||
} else {
|
||||
let filename = file_path.file_name().unwrap().to_str().unwrap();
|
||||
let extension = extension_result.unwrap();
|
||||
if extension == "js" && !filename.contains(&"module") && !filename.contains(&"min") {
|
||||
// println!("{} => {}", file_path.display(), file_build_path.display());
|
||||
let result = std::fs::read_to_string(file_path);
|
||||
if result.is_err() {
|
||||
std::fs::copy(file_path, file_build_path).unwrap();
|
||||
} else {
|
||||
let content = minify(result.unwrap().as_str()).to_string();
|
||||
let mut file = File::create(file_build_path).unwrap();
|
||||
file.write_all(content.as_str().as_bytes()).unwrap();
|
||||
}
|
||||
} else {
|
||||
std::fs::copy(file_path, file_build_path).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{}", "obfuscation_static Completed");
|
||||
}
|
||||
|
||||
pub fn envoye_un_mail(tmpl: &Data<Tera>, template_name: &'static str, context: &Context, subject: &str, email_adress: &str) {
|
||||
let body = tmpl.render(template_name, context).unwrap();
|
||||
|
||||
let email = Message::builder()
|
||||
.from("Site DrWhy <noreply@imaplan.fr>".parse().unwrap())
|
||||
.to(format!("<{}>", email_adress).parse().unwrap())
|
||||
.subject(subject)
|
||||
.multipart(MultiPart::alternative_plain_html(
|
||||
String::from(""),
|
||||
String::from(body),
|
||||
)).unwrap();
|
||||
|
||||
let sender = SmtpTransport::starttls_relay("ssl0.ovh.net").unwrap()
|
||||
.credentials(Credentials::new(
|
||||
"noreply@imaplan.fr".to_string(),
|
||||
"24rueduMoulin".to_string(),
|
||||
))
|
||||
// .authentication(vec![Mechanism::Plain])
|
||||
.pool_config(PoolConfig::new().max_size(20))
|
||||
.build();
|
||||
|
||||
let result = sender.send(&email);
|
||||
if result.is_ok() {
|
||||
println!("Mail envoyé");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
std::fs::create_dir_all(&dst)?;
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let ty = entry.file_type()?;
|
||||
if ty.is_dir() {
|
||||
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||
} else {
|
||||
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
5
static/css/pico.min.css
vendored
Normal file
5
static/css/pico.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/css/pico.min.css.map
Normal file
1
static/css/pico.min.css.map
Normal file
File diff suppressed because one or more lines are too long
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
static/img/404.png
Normal file
BIN
static/img/404.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
26
static/img/default_project.svg
Normal file
26
static/img/default_project.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="200"
|
||||
height="150">
|
||||
<rect
|
||||
width="199"
|
||||
height="149"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
id="rect2824"
|
||||
fill="#ffffff" stroke="#999999" stroke-width="1"/>
|
||||
<text
|
||||
x="64.648438"
|
||||
y="79.863152"
|
||||
id="text2818"
|
||||
font-size="24"
|
||||
font-weight="bold" text-align="center" text-anchor="middle" fill="#999999" stroke="none" font-family="Sans"><tspan
|
||||
x="100"
|
||||
y="60"
|
||||
id="tspan2820">No image</tspan><tspan
|
||||
x="100"
|
||||
y="90"
|
||||
id="tspan2822">available</tspan></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 653 B |
53
static/img/default_user.svg
Normal file
53
static/img/default_user.svg
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="79.0665"
|
||||
height="79.7577"
|
||||
viewBox="0 0 79.0665 79.7577"
|
||||
version="1.1"
|
||||
id="svg16"
|
||||
sodipodi:docname="profile.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview18"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="10.945652"
|
||||
inkscape:cx="39.559087"
|
||||
inkscape:cy="39.878849"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg16" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<style
|
||||
id="style2">.a{fill:#e6e6e6;}.b{fill:#fefefe;}.c{fill:#999;}</style>
|
||||
</defs>
|
||||
<path
|
||||
class="a"
|
||||
d="M79.0543,37.12v4.9657a.8926.8926,0,0,0-.1271.3012,26.0586,26.0586,0,0,1-.4126,3.7331,37.5548,37.5548,0,0,1-3.2136,10.348,38.1152,38.1152,0,0,1-6.145,9.2679,40.12,40.12,0,0,1-6.2572,5.7124q-5.5157-2.3708-11.03-4.745a.713.713,0,0,0-.964.2136c-.209-.0924-.4233-.1745-.6261-.2788-1.2993-.6689-2.5082-1.4831-2.611-3.09-.1141-1.7846-.0624-3.58-.0655-5.3711a1.0121,1.0121,0,0,1,.35-.6539,8.2157,8.2157,0,0,0,2.1645-3.7543c.1412-.5986.3941-1.17.5835-1.7584a.7384.7384,0,0,1,.5736-.5816A2.6682,2.6682,0,0,0,52.991,50.06a7.0328,7.0328,0,0,0,.9081-2.644c.1019-1.1758.3787-2.4973-1.0831-3.1752.037-.2255.0628-.4535.1126-.6761a22.9025,22.9025,0,0,0,.4608-8.5634,11.1106,11.1106,0,0,0-3.4463-6.9251,4.0737,4.0737,0,0,0-2.8622-1.1332,1.0433,1.0433,0,0,1-.7391-.28,10.4977,10.4977,0,0,0-7.8888-2.25,14,14,0,0,0-6.4809,2.0424,12.7213,12.7213,0,0,0-5.6264,7.2722c-.8128,2.5464-.4831,5.1113-.1844,7.6763.1133.9734.3015,1.9381.4556,2.9068A1.2562,1.2562,0,0,0,25.26,45.4068,6.5745,6.5745,0,0,0,26,49.7769a2.8631,2.8631,0,0,0,1.8267,1.6449.8863.8863,0,0,1,.6367.7062c.5612,1.8581,1.0687,3.7613,2.5015,5.1879a1.9249,1.9249,0,0,1,.508,1.5571q-.0225,2.2406,0,4.4815a2.3378,2.3378,0,0,1-.3419,1.3109,4.8023,4.8023,0,0,1-2.6947,2.1126,1.1931,1.1931,0,0,0-.8509.058c-2.4448,1.0142-4.9032,1.9954-7.3514,3.0016A35.06,35.06,0,0,0,16.4108,71.48a1.5989,1.5989,0,0,1-.3063-.14A41.8,41.8,0,0,1,9.7334,65.41a37.6818,37.6818,0,0,1-4.6512-6.5961A38.6067,38.6067,0,0,1,.1775,42.1228a38.366,38.366,0,0,1,.554-9.714A40.0525,40.0525,0,0,1,5.5272,19.5707,39.3737,39.3737,0,0,1,28.6234,1.6257,36.0658,36.0658,0,0,1,36.8946.1873a43.0772,43.0772,0,0,1,8.6359.352,39.3771,39.3771,0,0,1,11.1581,3.414,38.9334,38.9334,0,0,1,11.7393,8.6521,44.5451,44.5451,0,0,1,3.9365,4.9161,37.8665,37.8665,0,0,1,3.6128,6.693,39.2442,39.2442,0,0,1,2.8269,10.6882A7.8147,7.8147,0,0,0,79.0543,37.12Z"
|
||||
id="path6" />
|
||||
<path
|
||||
class="b"
|
||||
d="M28.4367,66.778a4.8023,4.8023,0,0,0,2.6947-2.1126,2.3378,2.3378,0,0,0,.3419-1.3109q-.0206-2.2406,0-4.4815a1.9249,1.9249,0,0,0-.508-1.5571c-1.4328-1.4266-1.94-3.33-2.5015-5.1879a.8863.8863,0,0,0-.6367-.7062A2.8631,2.8631,0,0,1,26,49.7769a6.5745,6.5745,0,0,1-.7407-4.37,1.2562,1.2562,0,0,1,1.3572-1.0962,14.4987,14.4987,0,0,1,1.3355,1.8033c.09.12.1056.3553.3084.3066.1809-.0434.1512-.2478.1748-.4079.1617-1.0966.3324-2.1919.5045-3.287a13.27,13.27,0,0,0,.1991-4.6114,2.135,2.135,0,0,1,.57-2.0718,2.4862,2.4862,0,0,0,.5867-.7469c.31-.7049.8855-.7106,1.4952-.6459,2.4682.2619,4.9431.45,7.4221.5315a49.0086,49.0086,0,0,0,7.2993-.4645,2.8231,2.8231,0,0,1,3.0035,1.44,3.0766,3.0766,0,0,1,.5142,2.4272,8.4555,8.4555,0,0,0,.0028,2.5875c.2542,1.61.4956,3.2216.7352,4.8336.0236.1588-.0128.3629.1692.4112.2069.055.2414-.1658.3025-.3008A3.1606,3.1606,0,0,1,52.816,44.241c1.4618.6779,1.185,1.9994,1.0831,3.1752a7.0328,7.0328,0,0,1-.9081,2.644,2.6682,2.6682,0,0,1-1.7173,1.3689.7384.7384,0,0,0-.5736.5816c-.1894.588-.4423,1.16-.5835,1.7584a8.2157,8.2157,0,0,1-2.1645,3.7543,1.0121,1.0121,0,0,0-.35.6539c.0031,1.7909-.0486,3.5865.0655,5.3711.1028,1.6067,1.3117,2.4209,2.611,3.09.2028.1043.4171.1864.6261.2788a4.4593,4.4593,0,0,1-.7173,1.594A10.4891,10.4891,0,0,1,43.93,73.2392a16.44,16.44,0,0,1-6.69.4425,10.6211,10.6211,0,0,1-7.05-3.5808A6.9041,6.9041,0,0,1,28.4367,66.778Z"
|
||||
id="path10" />
|
||||
<path
|
||||
class="c"
|
||||
d="M28.4367,66.778a6.9041,6.9041,0,0,0,1.7524,3.3229,10.6211,10.6211,0,0,0,7.05,3.5808,16.44,16.44,0,0,0,6.6905-.4425,10.4891,10.4891,0,0,0,6.2578-4.7282,4.4593,4.4593,0,0,0,.7173-1.594.713.713,0,0,1,.964-.2136q5.5119,2.3794,11.03,4.745a38.1426,38.1426,0,0,1-12.9581,6.2263A40.822,40.822,0,0,1,42.11,78.9469a44.34,44.34,0,0,1-6.7561-.15,38.3686,38.3686,0,0,1-11.1365-2.8259,40.3042,40.3042,0,0,1-7.5312-4.1885,1.7468,1.7468,0,0,1-.2754-.3027,35.06,35.06,0,0,1,3.8236-1.6419c2.4482-1.0062,4.9066-1.9874,7.3514-3.0016A1.1931,1.1931,0,0,1,28.4367,66.778Z"
|
||||
id="path12" />
|
||||
<path
|
||||
class="c"
|
||||
d="M52.816,44.241A3.1606,3.1606,0,0,0,51.24,46.1155c-.0611.135-.0956.3558-.3025.3008-.182-.0483-.1456-.2524-.1692-.4112-.24-1.612-.481-3.2238-.7352-4.8336a8.4555,8.4555,0,0,1-.0028-2.5875,3.0766,3.0766,0,0,0-.5142-2.4272,2.8231,2.8231,0,0,0-3.0035-1.44,49.0086,49.0086,0,0,1-7.2993.4645c-2.479-.081-4.9539-.27-7.4221-.5315-.61-.0647-1.1855-.059-1.4952.6459a2.4862,2.4862,0,0,1-.5867.7469,2.135,2.135,0,0,0-.57,2.0718,13.27,13.27,0,0,1-.1991,4.6114c-.1721,1.0951-.3428,2.19-.5045,3.287-.0236.16.0061.3645-.1748.4079-.2028.0487-.2183-.1865-.3084-.3066a14.4987,14.4987,0,0,0-1.3355-1.8033c-.1541-.9687-.3423-1.9334-.4556-2.9068-.2987-2.565-.6284-5.13.1844-7.6763a12.7213,12.7213,0,0,1,5.6264-7.2722,14,14,0,0,1,6.4809-2.0424,10.4977,10.4977,0,0,1,7.8888,2.25,1.0433,1.0433,0,0,0,.7391.2805,4.0737,4.0737,0,0,1,2.8622,1.1332,11.1106,11.1106,0,0,1,3.4463,6.9251,22.9025,22.9025,0,0,1-.4608,8.5634C52.8788,43.7875,52.853,44.0155,52.816,44.241Z"
|
||||
id="path14" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
185
static/img/logo.svg
Executable file
185
static/img/logo.svg
Executable file
@@ -0,0 +1,185 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 857.3 826.7" style="enable-background:new 0 0 857.3 826.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Calque_3">
|
||||
<ellipse id="path82" class="st0" cx="432.8" cy="414.9" rx="430.4" ry="416.7"/>
|
||||
</g>
|
||||
<g id="svg5302" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
|
||||
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview5304" inkscape:current-layer="svg5302" inkscape:cx="476.73494" inkscape:cy="433.80415" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="1043" inkscape:window-maximized="1" inkscape:window-width="1920" inkscape:window-x="1920" inkscape:window-y="0" inkscape:zoom="0.80747031" objecttolerance="10" pagecolor="#ffffff" showgrid="false" showguides="false">
|
||||
<inkscape:grid id="grid84" type="xygrid"></inkscape:grid>
|
||||
</sodipodi:namedview>
|
||||
<path id="path5312" d="M259.9,36c-3.8,1.8-27.5,9.8-14.9,10.1C252.4,45.2,258.4,43.3,259.9,36z M362.7,100
|
||||
c-12.8-0.9-13.1,5.9-1.3,5.3c10.4,1.5,16.3,2.5,24,6.7c0.5,4.8,14.1-4.9,4.3-4C379.4,103.7,375.7,100.5,362.7,100z M344,102.7
|
||||
c-1.7,0-1.7,2.7,0,2.7C345.7,105.4,345.7,102.6,344,102.7z M325.3,108c-1.7,0-1.7,2.7,0,2.7C327.1,110.7,327.1,108,325.3,108z
|
||||
M314,110.7c-18.2,10.8-45.1,6.5-62,18.6c-3.8-0.8-50.6,32.5-28,21.3c15.8-5,27.6-16.6,42.7-21.3
|
||||
C274.5,125.9,323.2,124.6,314,110.7z M394.7,113.3c-1.7,0-1.7,2.7,0,2.7C396.4,116,396.4,113.3,394.7,113.3z M405.3,113.3
|
||||
c-1.7,0-1.7,2.7,0,2.7C407.1,116,407.1,113.3,405.3,113.3z M446.7,113.3c-9.7,4.7-31.8,4.4-42.7,6.7c14.3,9.3,51-5.1,66.7,5.3
|
||||
c9.2,3,18,7.3,26.1,12c8.8,4.6,16,15.6,24.6,17.3c7.2,2.6,18,3.5,24,8c9.2,3.8,12.1,11.7,24.3,12c16.4-1.1,22,13.6,36.3,16
|
||||
c10.9,1.1,16.4,5.5,25.1,10.7c7.5,7.9,14.1,18,15.7,29.3c1.8,11.7-17.8,19.5-21.3,7.4c-7.5-4.7,3.2-13,5.3-19.3
|
||||
c-0.9-6.8-14.3-15.9-19.3-13c-4.4,5,2.4,0.1-2,10.3c0.2,5-10.1,8.4-13.3,5.2c-18.8-10.7,2.4-19-4.6-26.1
|
||||
c-3.2-3.4-18.5-8.1-12.8,0.8c5.8,9.1-0.1,20.7,10.7,29.3c8.1,6.7,6.5,12.5,14.6,16c9.1,3.2,14.6,21.1,24.6,21.3
|
||||
c7,0.4,9.6,5.1,15.1,8c10.8,5.4,14.4,19.7,19,28.8c7.6,12.8,25.3,18.8,38.7,21.9c5.4-0.8,5.4,5.7,0.6,5.3
|
||||
c-7.2-0.2-11.9,12.2-11.6,15.4c2.1,5.9,7.2,7.3,7,15.5c-1.7,9.2-4.6,15.2,1.3,22.4c2.7,17.5,16.6,19.3,14.7,40.7
|
||||
c4.5,40.9-41.3,14.4-12,4.6c5.9,1.9,3.6-5.4,4-8.7c2.2-7.6-7.2-19.4-8-12.6c-0.9,3-2.8,5.5-4,8c-2.2,4-1.9,4.7-6.7,6.7
|
||||
c-0.2,2-5.4,0.9-5.9,3c-0.6,1.3-0.9,1.3-1.6,0c-14.8-3.1-7.7-24.1,5.1-16c7.3,1.6,4.8-15.9,2.3-17.7c-1.1-6.3-2.7,1.4-9.3,0
|
||||
c-11.6-0.3-19.4-9.4-11.4-20.7c4.6-7.9,24.7,3.8,20.7-11.3c1.7-12.9-24.4-44.8-25.3-18.7c-2.6,20.7-37.1,26.8-32,2.7
|
||||
c2.1-4.8,5-7.2,11.4-8c16.2,0.2,8.8-37-4.8-38.7c-1.8-3-14.4-1.2-13.3,5.3c-0.4,11.4-22.9,6.9-21.3-4c-1.1-8.8,13.3-9,12.3-15.6
|
||||
c-1-3.7-15.8-21.9-17.6-9.7c0.6,14.5-24.8,27.8-26.7,8c-1.1-10.2,8.3-9.5,16-9.3c2-16.4-5.2-23.7-17-23.7
|
||||
c-3.4-6.3-18.4-0.3-20.8-1.9c-8.1-13.7,3.4-26.1,19.1-22.4c1.4-17.7-14.2-33-30.7-34.7c-15.4-4.9,3.4,39-24.6,40
|
||||
c-18.1-2,16.1,14.8,15.3,22c24.6,54.5-33.5,40.2-10.7,20.7c12-3.9,4.9-15.7-3.1-18.7c-9.4-8.4-6.3,15.8-16.1,16
|
||||
c-4.1,3.7-8.4,2.7-12.8,0c-2.1,0.7-5.6-8.1-4.3-7.4c4.2-16.2,19.8-0.4,17.7-15.9c1-13-9.6-16.1-16-9.9c-5.2,3.6-17.5-0.7-18.7-7.2
|
||||
c-11.9-21.2,19.1-15.8,16-27.4c-0.3-13.6-13.3-21.1-24-24c-11-3.7-6-0.5-5.3,8c-8.6,57.1-58.6,7.4-21.3-1.3
|
||||
c11.6,0.7,1.2-13.6,0-18.1c0-5-19.3-15.1-18.7-8.6c19.5,31.6-18.3,55.3-29.3,21.9c-2.6-4.6,12.2-13.7,6-13.9
|
||||
c-3.3-0.3-2.1-2.9,1.3-2.7c3.8-0.2,2.5,2,4.7,2.7c6.8,1.3-10.1-17.4-11.9-14.9c0.6,2-8.7-0.4-7.1-1c2.9-0.2,2-5.4-1-5.4
|
||||
c-3.1-1.7-5.4-2.2-8-4c-2-1.1-8.5-2.5-10.7-4c-9.2-1,12.6,14.1,10.7,16.6c3.3,2,6.3,22,8.5,12.4c0.4-1.7,0.3,2.9,0,10.2
|
||||
c2.6,25.3-24.4,28-32.4,10.9c-4.4-5.1-1.6-5.3,0-11.4c-0.8-5.4,4-2.8,5.3-5.3c-1.8-2.1-5-3.7,1.3-4c8.5,0.8,1-5.6-2.5-5
|
||||
c-4.3,0.5,0.1-4.9-4.6-7.6c-5.7-5.6-10.3-3.2-4.9,0.6c-7,5.2-2.5-10.1-10.7-10.7c-29.9-12.5,5.7,11,2.7,25.3c0,7,0.7,6.9,2.7,9.3
|
||||
c-3.9,3.1-11.3,3.7-15.4,6.7c-5.6-0.3-4.4-6.5-9.9-7.1c-6.9-0.7-3.8-9.6-0.7-13.4c12.4-16.2-33-14-46-14.1
|
||||
c0.5,21.3-9.3-10.8-18.7,11.2c-2.4,9-2.6-8.9-8-1.9c-5.5,3.2-11.5,1.5-15.2,1.3c-0.9,1,0,9-3.4,8c-3.6-0.1-1.7,2.8-5.3,2.7
|
||||
c-8.3,1-13.6,8-16.3,8c-4.4,2.3-8.8,4.4-13,6.7c-2.7,4.4-5.4-1.9-9.3-1.3c-4.7,0.4-2.9-2.5-6.7-2.7c-9.2,5.6,7.5,7.8,11.4,12.6
|
||||
c17.4,23.9-22,35-23.9,5.3c-8.6-5.2-15.2,3.2-22.4,6.1c-9.6,4.6,13.5,14,16.3,16c7.3,3.1,13.9,3.5,22.4,5.3c11.7-2,17.4-10,28.3-12
|
||||
c24.1-3.7,15.7,3.3,34.7-13.3c7.1-6.8,16.6-5.1,24-9.3c6.8,3.4,17.7,2.8,23.4,8c32.7,17.3-17.7,42.7-13.7,10.3
|
||||
c2.9-7.5-20.6-5-23-2.3c-4.2,0.2-1.9,4.6-5.5,6.8c-2.5,2.2-2.6,2.6-0.8,4.5c3.5,1.6,18.7,13.6,19.6,18.7c4.6,4.5,0,18.5,10.1,10
|
||||
c4.2,0.6,7.9-8.1,3.3-4.1c-3.2,4.7-9.7-10.9-1.4-9.3c2.6-2.2,14.5-2.3,17.3-4c5.7-3.5,13-7.9,18.7-10.7c20.7-6.6,41.6-15.6,64-18.7
|
||||
c12.7-10.1,18.5,2.5,9.3,4c-17.6,4.6-33.6,10.5-50.7,16c-14.9,5,14.3,10,13.9,16.8c6.5,4.2,12.6,0.4,16.8-2.1
|
||||
c3.2-1.4,10.5-2,13.3-4c13.6-5.6,30.9-7.9,45.3-12c8.7-1.3,23.8-2.5,33.3-4c8.2-0.1,10.4-1.5,9.3,8c-36.2,1.4-70.5,9.6-101.3,22.7
|
||||
c2.9,3.4,14.9,5.9,16,9.3c-1.6,4,10.2,1.7,10.7,4c7.5,1.8,2.1,6.9,12,6.7c24.8-6.8,48.1-10.3,74.7-13.3c10.3,0.7,10-0.6,6.7,5.3
|
||||
c1.1,3.9-7.5,1.7-8,4c-12.9,2-35.7,2.6-48,6.7c-3.5,0.9-23.5,4.1-12,5.3c7.4,1.2,2.3,9.3,15.2,8c16.4-2.3,23.5-6.6,40.8-8
|
||||
c11.3,1.3,9.7-6.9,18.7,0c1.7,4.9,20.5,2.1,22.7,0c9.8,6.9-31.5,5.8-36,8c-6.3,0.7-43.1,5.7-33.3,10.7c6.9,3.7,13.2,5.7,20,10.7
|
||||
c32,20.8,30.1,16.1,72,13.3c37,2.3,63.4,3.4,95.4,13.3c11-0.1,3.8,10.6-0.8,13.3c-5.9-5.1-27-4.9-34.7-6.7
|
||||
c-5.6-1.4-14.4-4.3-18.7-8c-3.5-2-35.6-1.3-37.3-2.7c-1.6,3.3-6.2-4.8-8,0c-0.5,2.1-6,0.4-5.3,4c-4,7.1-15.2-1.3,0,9.3
|
||||
c12.3,7.2,23.5,15.1,36,20c9.2,2.4,2.1,12.2,16,5.3c15.4-4.1-23.3-26.7-28-28c-3.6-1.8-7.8-2.5-12-4c-10.3-1.5,0.8-2.9,12-2.7
|
||||
c14-0.2,13.5,0.6,18.7,2.7c6.8,3.5,13.8,7.1,21.3,9.3c8.2,2.1,30.2,1.3,36.7,6.7c10.8,5.6,9.2,15,24,18.7
|
||||
c10.3,6.4,13.8,20.7,25.8,29.3c6.6-1.4,29,1.9,11.3,5.3c-5.1,0-6.8,0.7-10.7,4.6c-11.8,12.4,2,22.9,12.7,24.7
|
||||
c6.2-2.2,2.6,9.2,5.3,10.7c3.9,5.9-2,15.9-4,21.2c-6.2,10.8,3.2,1.1,8,0.1c10.2,8.4,9.8-7.3,16-10.7c4.4-1.7,8.3-3.9,11.8-8.2
|
||||
c8.1,7.3,14.6,16.4,18.8,24.2c2.3,7.9,14.6,10.8,17.3,19.3c3.1,3.7,3.7,9.6,7.4,12.7c18.2,4,24.9,57.4-1.4,42.1
|
||||
c-14.3-14.7,17.3-8.9,8.7-20.7c-11.6-23.9-7.5,7.4-24.7-5.9c-5.4-3.8-1.6-12.8,3.4-12.7c7.1-0.1,2.8-4.7,1.3-7.4
|
||||
c-3.3-8.2-4.3-6.3-6.7-0.6c-0.7,3.2-4.8,13.7-5.7,15.6c1.8,6.9,3.3,21.7,8.3,27c1.9,2.3,0.4,13.9,5.3,16c5.1,5.1,4.9,8.8,8,13.3
|
||||
c3.4,1.4-0.5,23-0.3,23.4c3.9,12.7,19.6,26.1,25.7,37.9c0.7,3.9,5,2.3,6.7,5.3c2.6-3.6,0.3-7.3-1.3-10.4
|
||||
c-2.9-7.7-19-22.2-22.7-29.6c-4.9-6.7,2.7-6.9,1.3-16c-1.1-10.6-2.8-14.9-8-22.7c-9-9.4,10.2,1.3,10.7-2.7
|
||||
c14.2-3.8,16.4-27.7,10.7-38.7c-12.5-20.3-31.8-39.8-45.3-60c-5.4-9.5-17.4-11.5-5.3-16c9.5-4.9,6,25.1,31.4,12
|
||||
c17.6-9.3-4.6-40.6-19.4-42.7c-7.9-2.8-18.5-2.9-24.7-8c-3.7-0.1-15.1-13.4-8.6-13.3c8,5.8,21.8,0.7,24-9.9
|
||||
c2.3-10.2,10.5,8,19.2,4.6c12.1-0.9,14.5-3.9,15.5-16c-1-20.5-25.9-41.8-45.7-35.7c-5.2-1.5-12.9-9.6-18.3-11
|
||||
c-6.9-2.9-15.1-1.8-21.3-8c-7-6.3-13.4-5.1-14.7-14.7c-0.9-8.7-14.5-25.2-23.3-29.3c-4.9-4.6-18.2-5.1-20.7-13.3
|
||||
c-0.3-9-30.1-15.1-18-21.4c4.5,0,12.6-8.1,12.6-12.6c-0.2-8.6,9.4,3.6,2.7,6c-6.9,7.1-1.6,25.1,8,26.7c14.4,7.1,30-10,24-22.8
|
||||
c-2.1-15.5-15.6-30.4-29.3-35.9c-5.9-4.4-18.4-3.4-24-9.3c-8.8-7.5-20.9-9.9-32-12c-5.6-3.4-13-8-18.7-10.7
|
||||
c-5.4-4.1-14.6-6.1-21.3-8c-12-3.8-23.5-17.3-35-22.7C479.5,118.1,460.6,118.7,446.7,113.3z M486,156c-22-2-1.4,13.6,0.6,21.6
|
||||
c4.3,6.5,5.6,14.5,8,22.4c0.9,10.7,2.6-4.9,13.3-6.7C517.6,183.2,504.4,151.3,486,156z M0.1,161.6c-0.2,1.5-0.1,6.3-0.1,8
|
||||
C0.3,169.6,0,161.7,0.1,161.6z M146.1,174.7c-8.2-0.8-4.5,2.9-7.4,5.3c-0.7-2.5-13-3.9-14.7-1.3c-6.6,1.6-13,5.5-19,9.3
|
||||
c-5.4,2.6,1.2,6.3-11.7,5.3c-11.1,3.8-19.3-2.9-29.7,8c9.8-0.1,7.6,1.2,12.4,2.6c2.3-2.1,10.8-2.5,13.3-4
|
||||
c14.6-0.9,18.4-5.7,29.3-12c7-4.7,17-5.4,25.3-8C152.2,186.3,163,175.5,146.1,174.7z M316,190.7c7.4-1.8,7.2,8.9,1.3,8
|
||||
c-2.2,0-4,0.6-4,1.3c-6.6,2.4-14.5,9.4-22.4,4.6C286.6,198.4,310.8,191.8,316,190.7z M729.3,196c-9.8,0.3-11.2,6.8-14.7,13.3
|
||||
c-2.7,6.8,4.6,17.3,15.8,15.9C753.4,226.7,747.1,195.5,729.3,196z M138.7,201.3c1.7,0,1.7,2.7,0,2.7
|
||||
C136.9,204,136.9,201.3,138.7,201.3z M731.2,204c7.1-0.7,11.4,7.8,5.5,12.7C726,228.1,711.7,202.8,731.2,204z M0,209.3
|
||||
c0,4-0.1,15.2,0.1,18.3C0,227.6,0.3,209.1,0,209.3z M253.9,214.8c0.2,2.9,7,9.6,0.8,10.5C250.5,223.9,248.1,215.9,253.9,214.8z
|
||||
M110.4,217.3c-33.8-1.4,5.4,16.4,2.9,30.7c-0.9,10.3-3.9,13.6-14.1,14.7c-9.1,0.4-14.1-8.2-11.2-13.3c2.8-6.4,9.5-9.3,4-16
|
||||
c-2.3-6.4-17.7-6.7-25-3.2c-0.6,5.7-3,20.3-14.3,21.9c-8.1,4.4-7.2,1.1-15.9-1.2c-13.1,18,4,33.2-3.4,51.9
|
||||
c-5.9,30.7-24.5,8.8-25.7,45.5c2.9-3.3,4.3-9.2,7.1-12.9c-0.1-6.3,9.6-5,12-8.6c4.7,2.3-8.7,19.7-9.3,24c-37,73.7-4.3,183.6,32,252
|
||||
c-1.6-3.6-2.3-8.9-4-12c-18-51.9-16.1-133.1,2.7-184c12.5-36.1,34.7-73.7,66-101.3c3-4.1,20.8-20.1,26.1-24c6-2.5,12.3-14,14.9-2.3
|
||||
c-25.8,26.5-62.5,54.6-78.9,89c-54.6,94-45.6,238,35.3,316c4.3,2.3,8.7,3.9,14,5.3c6.9,2.9,9.8,3.4,2.7-5.3
|
||||
c-9.7-12.3-16.3-24.8-24-37.3C82.6,605.8,65.9,558.5,64,508c-2.3-58.7,13-117,46.7-162.4c0.9-5.7,67.1-78.1,61.9-50.9
|
||||
C44.7,386.1,49.8,568.9,137.3,681.1c4.7,4.4,5.9,14.6,14.7,13.6c9.4,0.1,4.4,2.9,14.7,2.7c21.2,1-11-21.1-10.7-29.3
|
||||
c-6.7-12.7-12.6-26.2-17.3-40c-19.7-62.2-12.5-139,10.7-197.3c13.4-32,28.2-61.6,53.3-90.7c6.8-8.1,29.6-34.3,38.7-40
|
||||
c10.6-5.3,15.5-15.6,13.3-28.6c0.2-23.1-0.3-13.9-13.3-10c-3.6,4.6-4.7-2.1-9.3-1.3c-7.6-2.2-12-14.9-6.7-21.3
|
||||
c6.9-7.5-5.5-15.8-14.7-16c-9.4,0.8-16.5,4.7-22.7,10.2c0.9,2-6.8,7-4.3,8.1c3.3,2.9,3.5,10.3,5.7,13.7c7.1,12.7-8.1,35.9-22.7,28
|
||||
c-12.2-3.7-8-21.5,3.3-25.3c4.9-1.9,0.1-12.5-3.3-13.3c-7.1-1.3-7.9-9.9-16-1.3c-9.4,11.2-27.9-0.4-20-12
|
||||
C136.3,223.6,117.4,217.4,110.4,217.3z M217.3,233.3c6.2,1.1-0.6,2.1,0,6.1c-1.2,9.8-13.1,9.1-18.7,13.9c-2.6,4.8-2.3-4-4.3-4.3
|
||||
C195.4,241,211.4,237.4,217.3,233.3z M277.3,233.3c-1.7,0-1.7,2.7,0,2.7C279.1,236,279.1,233.3,277.3,233.3z M82.4,233.8
|
||||
c13.1,4-10.9,21.1-14.4,16.9C73.4,243.7,72.6,237.9,82.4,233.8z M272,236c-1.7,0-1.7,2.7,0,2.7C273.7,238.7,273.7,236,272,236z
|
||||
M122.1,244c10.4,6.2,6.3,10.9-3.4,6.7C120.5,249.4,121.5,246.1,122.1,244z M558.7,246.7c9.8-1.4,10.8,16.6,2.7,17.3
|
||||
C553.5,270.4,543.6,244.9,558.7,246.7z M217.3,254.7c11.4,8.9-16.1,15.7-20,21.3C191.2,263.6,209.6,259.1,217.3,254.7z M56,257.4
|
||||
c2.2,4.3-11.7,11.8-16.3,13.3c-3.1,0-3.7-0.6-3.7-3.4C33.7,257.3,52.9,262.3,56,257.4z M81.1,260c16.1,6-11,18.5-15.6,24
|
||||
c-8,1-22.8,31.5-26.7,24C42.3,287.1,64.4,272.2,81.1,260z M541.3,265.3c1.9,4.8,27.4,3.2,20.8,8.8c-1.5,2.5-17.1,1.8-22.1,1.9
|
||||
C540.1,273.8,539.6,265.6,541.3,265.3z M602.7,276.5c6.9,4.1,2.1,3.9-1.3,6.2C597.5,287.9,598.3,276.7,602.7,276.5z M20,294.9
|
||||
c0,0.8-1.3,2.6,0,2.4C20,297.3,20,294.9,20,294.9z M252.7,302.7c-111.4,90.2-160.3,274-68,390c4.2,7.6,15,8.1,22.7,7.3
|
||||
c23.1,1.3,20.7,4.9,10-10.7c-9-12.1-17.6-24.7-24-37.3c-40-73.2-23.5-174.8,13.3-245.6c5-10.5,11.2-21.2,18.7-31.4
|
||||
c0-2.2,3.2-5.6,5.3-5.6c9.6-3.5,9.1-14.1,17.4-21.3C259,344.4,254.9,303,252.7,302.7z M844,308.8c-0.1,5.5,0.8,9.9,2.7,11.4
|
||||
C845.8,316.4,845,312.6,844,308.8z M721.3,332c49.5-4.7,44.3,60.8,21.3,33.4c-3.6-9.5,11.8-21.3-11.3-22.8
|
||||
c-4.8-0.1-9.9,0.9-6.1,2.7c1.9,0.1,0.8,16.8,3,16.6c1.3,0.6,1.3,0.9,0,1.6c-2.1,0.4-1.1,8.3-3,8.6c-2,4.3-2.3,4.7-6.7,6.7
|
||||
c-10,6.3-24.5-20.1-10.1-22.7C718.3,352.6,723.3,344.2,721.3,332z M704.8,332c11.6-0.1,15.3,16.5,2.2,15.9
|
||||
C694.6,350.3,694.1,331.7,704.8,332z M310.7,342.7c-2.7,0-2.7,0.9-2.7,160c-2.5,216.6,11.6,138.4-32,157.3
|
||||
c-20.9-8.4-2-20.5-10.7-21.3c-8.6,5.5-6.5,1.3-9.3-5.3c-1.9,7-0.8-111-2.7-104c-1.6,0.2-1.8-8.2,0-8c2.1,0,1.8-31.6-0.3-32.6
|
||||
c2.6,2.3,2.3-32.3-1-20.8c-19.3,34.8-31.5,86.5-25.3,130.7c3.2,20.4,8.5,41.3,17.3,58.7c1.9,4.5,28.2,57.8,34.7,42.7
|
||||
c-1.5-3.8-19.5-37.1-10.7-37.3c3.2,2.4,7.9,1.3,8,6.7c-0.1,5.4,2.8,2.6,2.7,8c0.5,4.4,2.6,6.1,4,9.3c2,2,1.6,8.3,4,10.1
|
||||
c2.1,8.3,15.8,9.1,6.7-2.1c-5-7.7-5.6-19.3-11-27c-2.5-1.4,10.6-13.8,9.6-3.7c3.1,13.7,8.6,24.6,13.3,37.3
|
||||
c2.5,22.3-161.5,3.1-178.7-2.7c-26-10,29.2,31.9,39.9,40.9c23,3.7,79.3,4.4,101.5-0.9c7.8-3,24.4-1.6,30.7-6.7
|
||||
c3.1,2-1.3,11.3-5.3,10.7c-33.9,6.6-29.9,7.4-77.3,8c-68.3-8.3-3.8,23,22.7,22.7c-4.3,7.2-9.2,7.2,2.3,11.2
|
||||
c2.5-11.9,16.2-21.2,27.1-24.6c12.7-1.6,19.6-2.3,29.3-10.7c1-2.5,6.1,0,5.3-4.8c-1.6-2.9,8.9-10.4,7-12.2
|
||||
c-3.1-4.8-1.2-21.9,1-25.4c2.4-8.2,7.9-14.6,16-16.3c1.2-5.1,20.1,10.1,10.1,14.1c-13.3,6.3-24.9,3.4-14.1,21.9
|
||||
c2-1.7,17.7-7.4,20-4c18.2,6.5,1.9,25.8-8,12c-7.7-9.9-30.4,12.2-2.7,12c19.6,4.2-2,30-8,10.7c-1.1-11.5-16.9-5.4-18.7,1.6
|
||||
c2.2,4.9,6.1,4.3,5.3,13.4c-0.2,11.2-3.1,7.7-11.4,10.4c-14,3-10.7-20.4-20.6-14.7c-10.4,4.5-12.8,6.5-17.3,17.3
|
||||
c-9.7,15.2,22.5,8.9,25.3,12c30.3,23.8,4,1.8,24,4c65-11.9,130.2-25.4,197.3-30.7c11.4-3.1,126.3-0.8,133.3,2.6
|
||||
c18.8-8.2-24.8-7.9-32-10.6c-69.6-9.4-145.4,1.5-212,14.7c-20.1,4.1-40.2,7.9-60,12c-11.8,2.4-30.4,8.9-42.7,5.3
|
||||
c-10.4-2.1-0.8-9.4,3.2-5c1,2.7,3.2-3.1,7.6-1.7c12.8-3,22.5-5.9,35.9-8c74.1-15.8,130.6-27.6,212-29.3c47.8,1.4,55.1,2.3,93.3,9.3
|
||||
c7.3,4.8,34.3-1.1,12-6.7c-87.4-36.7-196.9-24.9-286.7,0c-8,1.6-12.6,3.2-20,5.3c-8.8-0.5-2.8-12.5,5.9-10.7
|
||||
c12.4-2.2,20.6-5.6,32.8-8c42.3-10.9,90.5-17.3,135.3-20c-46.3-8.6-118.4-3.5-162,8c-4.5,6.1-15.3-12.1-1.3-9.3
|
||||
c22.7-4.4,44.7-7.4,68-10.7c32.8-7.4-96.9,3.9-86.7-2.7c1.9-7.6,6.9-3.8,12-6.7c-5-9.3-7.6-19.7-10.7-30.7
|
||||
c-0.2-8.1-8.5-20.6-2-26.1c9.6-9.2,7,16.5,10,20.8c2.8,11.8,6.5,21.3,10.7,32c-3.8,3.7,21.9,3,21.3,1.3c-0.9-2.1-3-1.2-2.7-5.3
|
||||
c-4.6-14.4-19.2-67.4,3-72.3c5-2.4-1.3,3.2-0.4,9.8c-4.2,16.3-1.9,34,2.7,49.2c2.1,6.9,3.6,11.9,7.2,18.1
|
||||
c2.2,2.7,15.6,2.5,10.1-0.8c-9.4-19.3-9.6-47.9-5.3-69.3c2.7-21.5,13.5-24.8,30.4-34.7c18.2-9.5,34.3-19.5,52.1-29.3
|
||||
c6.8-1.5,11.9-10.4,18.9-10.7c6.1,0.9,2-5.2,6.7-5.3c10.9-3.8,21.8-13.2,32-17.3c16.3-14,38.2-12.9,58.7-8c36,6.1,66.9,17.5,96,40
|
||||
c11.6,7.5,22.7,26.1,37.3,17.3c8.4-2.5,9.8,22.8,13.3,4.6c-1.5-21.1-15.2-4.7-26.1-13.2c-7.5-5.5,1.6-12.8,8-12.7
|
||||
c4.7,0,4.7,0,4.7-6.1c1-25.1-26.4,14.5-29.3-9.9c-0.5-15.9,15.9-0.1,18.7-7.9c-1.1-2.7,4.6-3.8,1.7-4.9c-4.1-3.1,1.9-10-7.7-8.6
|
||||
c-24.1-0.8-8.6-35.1,6.4-19.7c11.5-2.4,2.8-31.2-8.7-33.7c-5.2,5.2-17.6,5.9-18.3,15.3c-0.8,12.3-24.2-11.2-13.3,4.7
|
||||
c8.9,11.5,6.1,35.2-10.7,38.7c-10,0.5-10.4-13.7-2.1-15c14.9,8.7,10.4-22.3,0.8-11.7c-6.1,7-18.2,6.5-20-4c0.3-8.8,11.1-7.8,14.5-1
|
||||
c2.4-1.9,10.1-10.6,5.5-10.9c-2.9-1.6,0.5-6.3-6.1-5.3c-22.9-7.6-14-25.9,7.8-20.2c6.6-8.6-1-26.4-10.4-30.4
|
||||
c-1.6,7.6-7.3,13.8-15.7,16c-3.7,0-3.7,0-3.7-9.3c-0.9-13.9,2.9-6.7,8.8-11.2c3.9-7.2,1.1-15.1-2.7-20.2c-8.9-8.5-3.9,13.2-12,11.4
|
||||
c-12.6,6.8-25.8,16.8-39.4,22.7c-30.7,19-68.7,40.3-101.3,58.7C462.5,511.2,433,529.6,401,548c-8.6,4-17.7,10.6-26.4,14.7
|
||||
c-7.3,2.5-3.7,8.1-13.3,6.7c-0.7-24.8,2.2-39.3,0-56.1c0-7.4,1.4-10.1,3.2-6.2c2.3,1.1,14.4-8,15.4-12.3
|
||||
c20.9-23.7,50.1-39.5,77.3-49.3c3.1-1.9,30.3-6,17.3-9.3c-5.4,0.1-2.6-2.8-8-2.7c-5.8-0.8-4.9-8.2-14.7-8
|
||||
c-33.4,8.7-67,34.9-89.9,64c-7.6-13.5,16.4-32.3,23.3-37.3c12.2-10.7,26.6-19.5,39.9-25.3c2.3-3.4,30.6-9.3,13.6-12
|
||||
c-8.7-2-13-6.7-20.3-10.7c-8.4-6.5-7.4,0.7-17.3,0c-8.6,0.5-12.7,1.9-16,7.9c-3.7,8.2-17.4,15.5-23.2,21.4
|
||||
c-6-15.2,13-19.7,21.9-26.7c4.7-5.7,16.2-4.7,17.3-12c1.7-5.2-15.3-7.9-16-5.3c-7.1,4.3-16.3,6.9-22.7,12
|
||||
c-7.8-12.4,12.4-12.8,14.7-21.9C356,365.8,331.8,355,310.7,342.7z M226.5,345.3c4.5,3.4,13.8,7.7,6.2,15.4
|
||||
C220.2,372.3,210.6,347.3,226.5,345.3z M667,345.8c30.8,17,4.5,4.1-6.4,16.1c-1.8,1.8-3.3,4.2-3.3,5.3c1.4,6.7-12.5-7.5-6-5.9
|
||||
C658.3,360.4,662.2,347.9,667,345.8z M736,348c1.7-0.1,1.7,5.4,0,5.3C734.3,353.4,734.3,347.9,736,348z M580.5,348.6
|
||||
c0.6,1.2,2.5,13.8,3.8,16c1.1,0.5-6.1,14.4,2.3,12.8c4.9,1,7.4,2.8,10.7,5.3C602.7,371.5,588.6,354.7,580.5,348.6z M254,356
|
||||
c-22.5,19.8-37,47-47.3,72c-15.9,34.3-23.2,69.2-26.7,110.4c1.8,21.3-11.2,17.4-2.7,33.6c6.6,21.8,9.9,46.8,20,66.7
|
||||
c2.1,1.4,4.9,15.9,6.7,8c-4-6.8-5.7-14.6-8-22.7c-2.8-10.5-3.9-17.1-5.3-29.3c0.6-10.5-7.9-14.9,4.6-17.3c5.5-3.2,2.2,15.3,4.8,16
|
||||
c1.7,13.7,3.9,31.9,9.3,44c7.3,21.4,17.8,40.4,32,59.7c-0.9,6.7,17.2,6.5,18.7,4.6c-3.4-3.3-5.1-7.2-8-11
|
||||
c-36.5-52.1-46.4-127.2-24-189.3c6.4-19.2,13.5-36.2,24-53.3c2.5-1.8,2.7-3.5,2.7-25.6c-0.5-16.6,1.6-32-5.3-18.1
|
||||
c-18.8,26.2-30.9,53.6-40,83.7c-5.7,6.8-6.3,70.3-16.8,58.1c-4.4-19.4,4.1-41.1,7.4-59.4c6-18.3,11.9-37.2,21.3-53.3
|
||||
C230.5,407.9,261.9,387.9,254,356z M609.9,364.6c-1.4,1.7,3.1,2.8,2.1,7.5c-0.9,5.7,5.6,6.7,1.3,10.6
|
||||
C606.6,404.1,652.8,401.1,609.9,364.6z M573.3,369.3c-1.7,0-1.7,2.7,0,2.7C575.1,372,575.1,369.3,573.3,369.3z M837.3,369.3
|
||||
c-16.3,0.6-22.8,31.1-0.3,29.3C859.8,401.1,854.1,370,837.3,369.3z M837.3,374.7c10.5,4.4,13.2,15-0.3,18.6
|
||||
C823.9,389.2,827.2,379,837.3,374.7z M726.7,406.7c13.3-0.8,43.3,20.3,31.4,34.1c-9.8,9.2-21.2-2.5-12.4-11.8
|
||||
c4.5-3.7-0.5-2.6-0.3-6.4c-1.3-4.1-12.4-7.1-16-3.9c2.5,2.4,4.4,3.2-0.6,8.7c-10.9,14.2-5.5-15.9-8.7-18.1
|
||||
C716,407.4,721.5,406.6,726.7,406.7z M397.9,409.9c5.3,2.6,12.8,8.6,6.9,16.2C391.5,434.8,384.9,415,397.9,409.9z M698.7,417.3
|
||||
c1.7,0,1.7,2.7,0,2.7C696.9,420,696.9,417.3,698.7,417.3z M660,441.3c5.7,1.6,14.9,8.7,0,8C645.1,450,654.3,442.9,660,441.3z
|
||||
M642.7,454.7c1.7-0.2,1.7,10.9,0,10.7C641,465.5,641,454.5,642.7,454.7z M444,460c-12,6.7-27.5,11.4-37.3,23.3
|
||||
c4.1,2,15.7-8.9,21.3-11.3C430.6,468,452.3,462.2,444,460z M704.2,476.2c1.8,3.5,7,0.7,6.5,6.4C708.9,493.2,692,483.1,704.2,476.2z
|
||||
M716.8,481.3c2.4,0.2,7.6,7.8,1.9,8C715.2,490.5,715.7,482.1,716.8,481.3z M402.7,484c-1.7,0-1.7,2.7,0,2.7
|
||||
C404.4,486.7,404.4,484,402.7,484z M612,489.3c8.4,2,16.5,3.7,24,6.7c4.4,3,35.8,21.2,13.3,16c-10-6.2-23.4-9.1-34.7-13.3
|
||||
c-3.6-2-13.3-2-16-5.3C603.2,491.7,606.3,490.4,612,489.3z M694.4,489.4c7.2-0.5,4.4,11.7-0.7,5.9
|
||||
C690.9,492.2,692,489.4,694.4,489.4z M749.3,521.3c9.4-1.4-1.2,13.1-1.3,5.3C747.5,523,742,522,749.3,521.3z M685.5,524
|
||||
c9.7-1.1,13.8,25.3,1.2,12C677.5,528.9,675.4,529.9,685.5,524z M192.6,553.3c8.4-0.3,5.5,3.7,8.8,8c-1.5,3.4-2.7,5.1-4.6,8.8
|
||||
C182,579.5,173.4,552.6,192.6,553.3z M708.8,553.3c1,0,4.6,3.6,4.6,4.6C710.6,560.8,705.9,556.1,708.8,553.3z M717.3,596
|
||||
c-1.9,1.9-13.6,6.8-8,10.7c3.5,6.6,4.9,13,14.7,13.3c11.7-1.6-4.3,9.6-4,16c-4.4,2.1,4.8,3.3,5.3,5.3c2.5-4.6,0.4-14.7,9.9-13.3
|
||||
c15.2-2.6-10.2-6.4,5.3-10.7c5.3-0.9-5-6.2,5.3-8c5.9-1.7-1.9-4.7-4.6,0c-6.7,9.9-30.9,4.1-24-8C719.1,601.4,719.1,595.9,717.3,596
|
||||
z M736,596c-1.7,0-1.7,2.7,0,2.7C737.7,598.7,737.7,596,736,596z M51.1,606.8c0.2,0.4,0.5,0.8,0.7,1.2
|
||||
C51.7,607.5,51.4,607,51.1,606.8z M752,609.3c-1.7,0-1.7,2.7,0,2.7C753.7,612,753.7,609.3,752,609.3z M766.1,620
|
||||
c-4,9.9-12.8,21.2-16.8,32c-3.2,2.1,4.8,17.4,0,21.3c-2.5,3.9-9.9,22.3-17.3,17.3c-4.5-1.9-11.5-6.3-6.1-12.8
|
||||
c7.6-6.2,8.4,4.4,14.9-1.1c3.5-9.6,3.8-17.6-6.1-10.1c-2.1,2.7-17.6,2.1-16-5.9c1-4.9-9.5-5-8,0.6c0.1,8.6,13.2,10.8,6.3,20.6
|
||||
c-1.1,4,7.7,12.4,12.3,14.1c0,1.7-0.5,1.8,7.8-1.4c15.5-6.5,22.2-20.6,18.9-32.1c2.8-7.5,18.4,6.7,19.4-5.2
|
||||
c-13.8,2.9-25.3-16.2-8.8-18.7C779.6,638.8,774,621.7,766.1,620z M752,625.3c-1.7,0-1.7,2.7,0,2.7C753.7,628,753.7,625.3,752,625.3
|
||||
z M0.1,632.7c0.1,0.1-0.3,16.7,0,16.7C0.1,649.4,0.1,632.7,0.1,632.7z M784,636c-1.7-0.1-1.7,5.4,0,5.3
|
||||
C785.7,641.4,785.7,635.9,784,636z M717.3,649.3c-1.7,0-1.7,2.7,0,2.7C719.1,652,719.1,649.3,717.3,649.3z M738.7,649.3
|
||||
c-1.9,0.2-0.7,5.7-3.4,5.3C716.2,665.3,748.5,659.2,738.7,649.3z M205.3,652c-1.7,0-1.7,2.7,0,2.7C207.1,654.7,207.1,652,205.3,652
|
||||
z M325.3,652c3.9,8.8,5.6,20.5,9.3,29.3c0.9,3.8-13,4.3-10.7-1.3C321.7,670,312.3,657.2,325.3,652z M0.1,673.2
|
||||
c-0.1,6.5-0.2,21.6,0,29.8C0.1,702.9,0.1,673.2,0.1,673.2z M0.1,744.7c0,2.5,0,4.8,0,6.6C0.1,751.4,0.1,744.7,0.1,744.7z
|
||||
M316,758.7c3.5-1.1,3.2,7.5,1.3,8c-1.8,0.1-0.9-5.4-2.7-5.3C312.3,760.9,313.4,758.4,316,758.7z M317.3,769.3c1.7,0,1.7,2.7,0,2.7
|
||||
C315.6,772,315.6,769.3,317.3,769.3z M0.1,776.1c0,1.2,0,2.3,0,3.3C0.1,779.4,0.1,776.1,0.1,776.1z M562.7,777.3
|
||||
c-72,0.8-98.6,6.3-161.3,16c-30.5,5.7-60.9,10.5-90.7,17.3c26.8,7.4,55,16,81.3,6.7c81.9-15.5,119.3-26.6,213.6-26.7
|
||||
c23.6-10.5,23.8-9.4,1-12C606.7,777.8,591.1,777.3,562.7,777.3z M586.9,798.7c-71.8,1.8-118.2,11.5-182.9,25.2
|
||||
C394.4,834.1,556.2,819.3,586.9,798.7z M0.1,802.8c-0.2,4.5-0.1,18.6-0.1,23.8C0.3,826.9,0,802.8,0.1,802.8z"/>
|
||||
</g>
|
||||
<g id="Calque_2">
|
||||
<line class="st1" x1="257.9" y1="246.6" x2="532" y2="406.7"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
1
static/index.css
Executable file
1
static/index.css
Executable file
@@ -0,0 +1 @@
|
||||
/*css content*/
|
||||
128
static/index.html
Normal file
128
static/index.html
Normal file
@@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>gps saver</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="startbutton" style="padding:0.5em; font-size: 1em;">Start</button>
|
||||
<button id="stopButton" style="padding:0.5em; font-size: 1em;">STOP</button>
|
||||
<div id="maps" style="width: 100vw; height: 100vh">
|
||||
</div>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||
<script>
|
||||
let startbutton = document.querySelector("#startbutton");
|
||||
let stopButton = document.querySelector("#stopButton")
|
||||
let mapElem = document.querySelector("#maps");
|
||||
let map = null;
|
||||
let allCoordinate = JSON.parse(`[]`);
|
||||
let previewCircle = null;
|
||||
let previewMarker = null;
|
||||
function onPositionReceive(coord)
|
||||
{
|
||||
console.log(coord)
|
||||
allCoordinate.push({
|
||||
timestamp: coord.timestamp,
|
||||
accuracy: coord.coords.accuracy,
|
||||
latitude: coord.coords.latitude,
|
||||
longitude: coord.coords.longitude,
|
||||
altitude: coord.coords.altitude,
|
||||
altitudeAccuracy: coord.coords.altitudeAccuracy
|
||||
});
|
||||
let timeStamp = coord.timestamp;
|
||||
coord = coord.coords;
|
||||
let diffLongitude = coord.accuracy / 111320 / 2;
|
||||
let diffLatitude = coord.accuracy / (400750000 * (Math.cos(coord.latitude) / 360)) / 2;
|
||||
let yMin = coord.longitude - diffLatitude;
|
||||
let yMax = coord.longitude + diffLatitude;
|
||||
let xMin = coord.latitude - diffLongitude;
|
||||
let xMax = coord.latitude + diffLongitude;
|
||||
// let url = "https://www.openstreetmap.org/export/embed.html?bbox=" + (xMin) + "%2C" + (yMin) + "%2C" + (xMax) + "%2C" + (yMax);
|
||||
|
||||
// mapElem.innerHTML = "";
|
||||
if(map == null) {
|
||||
map = L.map(mapElem).fitBounds([[xMin, yMin], [xMax, yMax]]);
|
||||
}
|
||||
if(previewCircle) {
|
||||
previewCircle.remove()
|
||||
}
|
||||
previewCircle = L.circle([coord.latitude, coord.longitude], {
|
||||
color: 'red',
|
||||
fillColor: '#f03',
|
||||
fillOpacity: 0.1,
|
||||
radius: coord.accuracy / 2
|
||||
}).addTo(map);
|
||||
|
||||
if(previewMarker) {
|
||||
previewMarker.remove()
|
||||
}
|
||||
previewMarker = L.marker([coord.latitude, coord.longitude]).addTo(map);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map);
|
||||
|
||||
}
|
||||
|
||||
let idgeoloc = null
|
||||
startbutton.addEventListener("click", ()=>{
|
||||
document.body.requestFullscreen();
|
||||
maps.innerHTML = "";
|
||||
navigator.geolocation.getCurrentPosition(onPositionReceive);
|
||||
idgeoloc = navigator.geolocation.watchPosition(onPositionReceive);
|
||||
});
|
||||
stopButton.addEventListener("click", ()=>{
|
||||
if(idgeoloc) {
|
||||
navigator.geolocation.clearWatch(idgeoloc);
|
||||
idgeoloc = null;
|
||||
}
|
||||
document.exitFullscreen()
|
||||
let coordinatesString = JSON.stringify(allCoordinate);
|
||||
// maps.innerHTML = coordinatesString;
|
||||
|
||||
var blob = new Blob([coordinatesString], {'type':'application/json'});
|
||||
let a = document.createElement("a");
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.download = "save.json";
|
||||
a.click();
|
||||
|
||||
let coord = allCoordinate[0];
|
||||
let diffLongitude = coord.accuracy / 111320 / 2;
|
||||
let diffLatitude = coord.accuracy / (400750000 * (Math.cos(coord.latitude) / 360)) / 8;
|
||||
let yMin = coord.longitude - diffLatitude;
|
||||
let yMax = coord.longitude + diffLatitude;
|
||||
let xMin = coord.latitude - diffLongitude;
|
||||
let xMax = coord.latitude + diffLongitude;
|
||||
mapElem.innerHTML = "";
|
||||
mapElem.class = ""
|
||||
if (map) {
|
||||
map.remove();
|
||||
}
|
||||
map = L.map(mapElem).fitBounds([[xMin, yMin], [xMax, yMax]]);
|
||||
let latLngs = [];
|
||||
for (let coordinate of allCoordinate) {
|
||||
latLngs.push([coordinate.latitude, coordinate.longitude])
|
||||
previewCircle = L.circle([coordinate.latitude, coordinate.longitude], {
|
||||
color: 'red',
|
||||
fillColor: '#f03',
|
||||
fillOpacity: 0.1,
|
||||
radius: coordinate.accuracy / 2
|
||||
}).addTo(map);
|
||||
previewMarker = L.marker([coordinate.latitude, coordinate.longitude]).addTo(map);
|
||||
}
|
||||
L.polyline(latLngs).addTo(map);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
15
templates/base.html.twig
Executable file
15
templates/base.html.twig
Executable file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{% block title %}{% endblock title %} - DrWhy</title>
|
||||
|
||||
{% block head %}
|
||||
{% include 'includecss.html.twig' %}
|
||||
{% endblock head %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}{% endblock body %}
|
||||
{% block footer %}{% include 'includescript.html.twig' %}{% endblock footer %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
28
templates/catch/404.html.twig
Executable file
28
templates/catch/404.html.twig
Executable file
@@ -0,0 +1,28 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Erreur 404{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<div class="text-center pt-5" style="height:100vh;">
|
||||
|
||||
<img src="/static/img/404.png" alt="404" class="zoom-hover" style="max-width:100vw;">
|
||||
|
||||
<i class="h5 d-block pb-4"> La page que vous essayez d'atteindre n'existe pas ou vous n'êtes pas autorisé à la voir.</i>
|
||||
|
||||
<hr class="mx-5 mb-4">
|
||||
|
||||
<a href="/" class="btn btn-primary mdi mdi-arrow-left me-2"> Page d'accueil</a>
|
||||
|
||||
<a href="/login" class="btn btn-outline-primary ms-2"> Login <span class="mdi mdi-arrow-right"></span> <a/>
|
||||
|
||||
</div>
|
||||
|
||||
<style media="screen">
|
||||
.zoom-hover {
|
||||
transition: transform .5s ease;
|
||||
}
|
||||
.zoom-hover:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
30
templates/catch/500.html.twig
Normal file
30
templates/catch/500.html.twig
Normal file
@@ -0,0 +1,30 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Erreur 404{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<div class="text-center pt-5" style="height:100vh;">
|
||||
|
||||
{{message_erreur}}
|
||||
|
||||
<img src="/static/img/404.png" alt="404" class="zoom-hover" style="max-width:100vw;">
|
||||
|
||||
<i class="h5 d-block pb-4"> La page que vous essayez d'atteindre n'existe pas ou vous n'êtes pas autorisé à la voir.</i>
|
||||
|
||||
<hr class="mx-5 mb-4">
|
||||
|
||||
<a href="/" class="btn btn-primary mdi mdi-arrow-left me-2"> Page d'accueil</a>
|
||||
|
||||
<a href="/login" class="btn btn-outline-primary ms-2"> Login <span class="mdi mdi-arrow-right"></span> <a/>
|
||||
|
||||
</div>
|
||||
|
||||
<style media="screen">
|
||||
.zoom-hover {
|
||||
transition: transform .5s ease;
|
||||
}
|
||||
.zoom-hover:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
11
templates/footer.html.twig
Normal file
11
templates/footer.html.twig
Normal file
@@ -0,0 +1,11 @@
|
||||
<footer class="footer mt-auto py-3 bg-transparent">
|
||||
<div class="container">
|
||||
<span class="text-muted">©DigitaruCamera : SORLIN Valentin.</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style media="screen">
|
||||
.footer {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
16
templates/forms/user/change_password.html.twig
Normal file
16
templates/forms/user/change_password.html.twig
Normal file
@@ -0,0 +1,16 @@
|
||||
<form class="" action="/change_password/{{user.password}}" method="post" enctype="multipart/form-data">
|
||||
{# Error message #}
|
||||
{% if message_error is defined %}<div class="text-danger text-center font-weight-bold mb-3">{{ message_error }}</div>{% endif %}
|
||||
{# Password #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control rounded-4" name="password" type="password" placeholder="mdp" required>
|
||||
<label for="password">Mot de passe</label>
|
||||
</div>
|
||||
{# Password again #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="repassword" type="password" placeholder="mdp" required>
|
||||
<label for="repassword">Confirmez le mot de passe</label>
|
||||
</div>
|
||||
{# Submit button #}
|
||||
<button class="w-100 mb-2 btn btn-lg rounded-4 btn-primary mt-3" type="submit">Envoyer</button>
|
||||
</form>
|
||||
13
templates/forms/user/forgotpassword.html.twig
Normal file
13
templates/forms/user/forgotpassword.html.twig
Normal file
@@ -0,0 +1,13 @@
|
||||
<form class="" action="/forgotpassword" method="post" enctype="multipart/form-data">
|
||||
{# Email #}
|
||||
<div class="form-floating mt-3 mb-3">
|
||||
<input class="form-control" name="email" type="email" placeholder="JohnDoe" required>
|
||||
<label for="email">Email</label>
|
||||
</div>
|
||||
{# Submit button #}
|
||||
<button class="w-100 mb-2 btn btn-lg rounded-4 btn-primary" type="submit">Envoyer</button>
|
||||
{# Forgot password #}
|
||||
<a href="/login" class="text-muted fs-6">Se connecter</a><br>
|
||||
{# Register #}
|
||||
<a href="/register" class="text-muted fs-6">Créer un compte</a>
|
||||
</form>
|
||||
43
templates/forms/user/login.html.twig
Normal file
43
templates/forms/user/login.html.twig
Normal file
@@ -0,0 +1,43 @@
|
||||
<form class="" action="/login" method="post">
|
||||
{% if message_login is defined %}<div class="text-danger text-center font-weight-bold mb-3">{{ message_login }}</div>{% endif %}
|
||||
{# Email #}
|
||||
<div class="form-floating mb-3">
|
||||
<input name="username" type="text" class="form-control rounded-4" id="floatingUser" placeholder="name@example.com">
|
||||
<label for="floatingUser">Nom d'utilisateur ou Email</label>
|
||||
</div>
|
||||
{# Password #}
|
||||
<div class="form-floating mb-3">
|
||||
<input name="password" type="password" class="form-control rounded-4" id="floatingPassword" placeholder="Password">
|
||||
<label for="floatingPassword">Mot de passe</label>
|
||||
</div>
|
||||
{# Error message #}
|
||||
{# Submit button #}
|
||||
<button class="w-100 mb-3 btn btn-lg rounded-4 btn-primary" type="submit">Se connecter</button>
|
||||
{# Forgot password #}
|
||||
<a href="/forgotpassword" class="color-hover">Mot de passe oublié ?</a><br>
|
||||
{# Register #}
|
||||
<a href="/register" class="color-hover">Créer un compte</a>
|
||||
{# Divider #}
|
||||
<hr class="my-4">
|
||||
{# Third party #}
|
||||
<h2 class="fs-5 fw-bold mb-3 text-center">Ou</h2>
|
||||
{# Google #}
|
||||
<div id="g_id_onload"
|
||||
data-client_id="723424966880-015kn0qncavlgbj4j3k1ggs3arn7tdkd.apps.googleusercontent.com"
|
||||
data-login_uri="/auth"
|
||||
data-auto_prompt="false">
|
||||
</div>
|
||||
<div class="g_id_signin d-flex justify-content-center mb-2"
|
||||
data-type="standard"
|
||||
data-size="large"
|
||||
data-theme="filled_black"
|
||||
data-text="sign_in_with"
|
||||
data-shape="rectangular"
|
||||
data-logo_alignment="left">
|
||||
</div>
|
||||
{# GitHub #}
|
||||
<button class=" w-100 py-2 mb-2 btn btn-outline-secondary rounded-4" type="submit">
|
||||
<span class="mdi mdi-github"></span>
|
||||
Se connecter avec GitHub
|
||||
</button>
|
||||
</form>
|
||||
78
templates/forms/user/register.html.twig
Normal file
78
templates/forms/user/register.html.twig
Normal file
@@ -0,0 +1,78 @@
|
||||
<form class="" action="/register" method="post" enctype="multipart/form-data">
|
||||
{# Error message #}
|
||||
{% if message_register is defined %}<div class="text-danger font-weight-bold mt-3">{{ message_register }}</div>{% endif %}
|
||||
{# UserName #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="username" type="text" placeholder="JohnDoe" required>
|
||||
<label for="username">Nom d'utilisateur</label>
|
||||
</div>
|
||||
{# Email #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="email" type="email" placeholder = "mail@example.com" required>
|
||||
<label for="username">Email</label>
|
||||
</div>
|
||||
{# Password #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control rounded-4" name="password" type="password" placeholder="mdp" required>
|
||||
<label for="password">Mot de passe</label>
|
||||
</div>
|
||||
{# Password again #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="repassword" type="password" placeholder="mdp" required>
|
||||
<label for="repassword">Confirmez le mot de passe</label>
|
||||
</div>
|
||||
{# Image #}
|
||||
<div class="form-group mt-3">
|
||||
<label for="input-image" class="input-label-img d-flex">
|
||||
<input name="image" type="file" accept="image/png, image/jpg, image/gif, image/jpeg"/>
|
||||
<span>Image de profil </span>
|
||||
</label>
|
||||
{# <label class="" for="image">Image</label>
|
||||
<input class="form-control" name="image" type="file"> #}
|
||||
</div>
|
||||
{# Submit button #}
|
||||
<button class="w-100 mt-3 mb-3 btn btn-lg rounded-4 btn-primary" type="submit">S'inscrire</button>
|
||||
{# Login #}
|
||||
<a href="/forgotpassword" class="color-hover">Mot de passe oublié ?</a><br>
|
||||
<a href="/login" class="color-hover">Se connecter</a>
|
||||
{# Divider #}
|
||||
<hr class="my-4">
|
||||
{# Third-Party #}
|
||||
<h2 class="fs-5 fw-bold mb-3 text-center">Ou</h2>
|
||||
{# Google #}
|
||||
<div id="g_id_onload"
|
||||
data-client_id="723424966880-015kn0qncavlgbj4j3k1ggs3arn7tdkd.apps.googleusercontent.com"
|
||||
data-login_uri="/auth"
|
||||
data-auto_prompt="false">
|
||||
</div>
|
||||
<div class="g_id_signin d-flex justify-content-center mb-2"
|
||||
data-type="standard"
|
||||
data-size="large"
|
||||
data-theme="filled_black"
|
||||
data-text="sign_in_with"
|
||||
data-shape="rectangular"
|
||||
data-logo_alignment="left">
|
||||
</div>
|
||||
{# GitHub #}
|
||||
<button class=" w-100 py-2 mb-2 btn btn-outline-secondary rounded-4" type="submit">
|
||||
<span class="mdi mdi-github"></span>
|
||||
Se connecter avec GitHub
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{# CSS #}
|
||||
<style media= "screen" >
|
||||
.input-label-img {
|
||||
border: 1px solid #ccc;
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
border-radius: 0.25rem;
|
||||
height: calc(3.5rem + 2px);
|
||||
padding: 1rem 0.75rem;
|
||||
}
|
||||
.input-label-img > input {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
26
templates/forms/user/register_google.html.twig
Normal file
26
templates/forms/user/register_google.html.twig
Normal file
@@ -0,0 +1,26 @@
|
||||
<form class="" action="/register_google/{{registrable_user.username}}" method="post" enctype="multipart/form-data">
|
||||
{# Email #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="email" type="email" value ={{registrable_user.email}} placeholder = "mail@example.com" disabled >
|
||||
<label for="username">Email</label>
|
||||
</div>
|
||||
{# UserName #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="username" type="text" placeholder="JohnDoe" value ={{registrable_user.email}} required>
|
||||
<label for="username">Nom d'utilisateur</label>
|
||||
</div>
|
||||
{# Password #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control rounded-4" name="password" type="password" placeholder="mdp" required>
|
||||
<label for="password">Mot de passe</label>
|
||||
</div>
|
||||
{# Error message #}
|
||||
{% if message is defined %}<div class="text-danger font-weight-bold mt-3">{{ message }}</div>{% endif %}
|
||||
{# Password again #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="repassword" type="password" placeholder="mdp" required>
|
||||
<label for="repassword">Confirmez le mot de passe</label>
|
||||
</div>
|
||||
{# Submit button #}
|
||||
<button class="w-100 mt-3 mb-3 btn btn-lg rounded-4 btn-primary" type="submit">S'inscrire</button>
|
||||
</form>
|
||||
97
templates/header.html.twig
Normal file
97
templates/header.html.twig
Normal file
@@ -0,0 +1,97 @@
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><img src="/static/img/logo.svg" alt="DrWhy" height="24" width="24"></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="#">Dr WHO(2005)</a></li>
|
||||
<li><a href="#">Dr WHO Classic(1963)</a></li>
|
||||
<li><a href="#">Torchwood</a></li>
|
||||
<li><a href="#">Sarah janes adventure</a></li>
|
||||
<li><a href="#">K-9</a></li>
|
||||
<li><a href="#">K-9 and Company</a></li>
|
||||
<li><a href="#">Class</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark" id="navbar" style="background-color: hsl(210deg 11% 20%) !important;">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="/static/img/logo.svg" alt="DrWhy" height="24">
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/#anchorStart">COMMENCER</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/#features">FONCTIONNALITES</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/#example">EXEMPLES</a>
|
||||
</li>
|
||||
</ul>
|
||||
{# <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3">
|
||||
<input type="search" class="form-control" placeholder="Search..." aria-label="Search">
|
||||
</form> #}
|
||||
|
||||
{% if user is defined %}
|
||||
<a href="/dashboard" class="btn btn-primary">Tableaux de bord</a>
|
||||
<div class="dropdown text-end ms-2">
|
||||
<a href="#" class="d-block link-light text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<img src="{{user.image}}" alt="avatar" class="rounded" width="38" height="38">
|
||||
</a>
|
||||
<ul class="dropdown-menu text-small" aria-labelledby="dropdownUser1" style="left:-6.5rem">
|
||||
<li><a class="dropdown-item" href="/profile/{{user.id}}">Profil</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-danger" href="/logout">Deconnexion</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary text-light ms-2" data-bs-toggle="modal" data-bs-target="#modalSignUp">Inscription</button>
|
||||
<button type="button" class="btn btn-black text-light ms-2" data-bs-toggle="modal" data-bs-target="#modalLogIn">Connexion</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{# <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal"> #}
|
||||
|
||||
{% if user is not defined %}
|
||||
{# Modal pour LogIn #}
|
||||
<div class="modal fade" role="dialog" id="modalLogIn">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content rounded-5 shadow">
|
||||
<div class="modal-header p-5 pb-4 border-bottom-0">
|
||||
<!-- <h5 class="modal-title">Modal title</h5> -->
|
||||
<h2 class="fw-bold mb-0">Connexion</h2>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-5 pt-0">
|
||||
{% include 'forms/user/login.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Modal pour SignUp #}
|
||||
<div class="modal fade" role="dialog" id="modalSignUp">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content rounded-5 shadow">
|
||||
<div class="modal-header p-5 pb-4 border-bottom-0">
|
||||
<!-- <h5 class="modal-title">Modal title</h5> -->
|
||||
<h2 class="fw-bold mb-0">Inscription</h2>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-5 pt-0">
|
||||
{% include 'forms/user/register.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
18
templates/includecss.html.twig
Normal file
18
templates/includecss.html.twig
Normal file
@@ -0,0 +1,18 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="dr_who_website">
|
||||
<meta name="author" content="https://sorlinv.fr/">
|
||||
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="apple-touch-icon" href="/static/favicon.png" sizes="180x180">
|
||||
<link rel="icon" href="/static/favicon.png" type="image/png">
|
||||
<link rel="mask-icon" href="/static/favicon.png" color="#7952b3">
|
||||
<meta name="theme-color" content="#333333">
|
||||
|
||||
<link href="https://vjs.zencdn.net/7.17.0/video-js.css" rel="stylesheet" />
|
||||
<link href="https://unpkg.com/@videojs/themes@1/dist/fantasy/index.css" rel="stylesheet">
|
||||
|
||||
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
||||
|
||||
<link rel="stylesheet" href="/static/css/pico.min.css">
|
||||
0
templates/includescript.html.twig
Normal file
0
templates/includescript.html.twig
Normal file
12
templates/index.html.twig
Executable file
12
templates/index.html.twig
Executable file
@@ -0,0 +1,12 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Acceuil{% endblock %}
|
||||
{% block body %}
|
||||
<body>
|
||||
|
||||
{% include "header.html.twig" %}
|
||||
|
||||
|
||||
{% include "footer.html.twig"%}
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
5
templates/mail/forgot_password.html.twig
Normal file
5
templates/mail/forgot_password.html.twig
Normal file
@@ -0,0 +1,5 @@
|
||||
<h3>DrWhy</h3>
|
||||
<div>
|
||||
<img src="{{uri}}/static/img/logo.svg" alt="logo">
|
||||
</div>
|
||||
<p>Bonjour {{user.username}}, pour rénitialiser votre mot de passe cliquez : <a href="{{uri}}/change_password/{{user.password}}">ICI</a></p>sssss
|
||||
2
templates/mail/welcome.html.twig
Normal file
2
templates/mail/welcome.html.twig
Normal file
@@ -0,0 +1,2 @@
|
||||
<h3>Bienvenue {{user.username}} sur le site DrWhy</h3>
|
||||
<p>Validez votre mail <a href="{{uri}}/user/validate/{{user.email_valid}}">ICI</a></p>
|
||||
30
templates/user/change_password.html.twig
Normal file
30
templates/user/change_password.html.twig
Normal file
@@ -0,0 +1,30 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Rénitialisation mot de passe{% endblock %}
|
||||
{% block body %}
|
||||
<head>
|
||||
<!-- Custom -->
|
||||
</head>
|
||||
<body>
|
||||
{% include "header.html.twig"%}
|
||||
<main>
|
||||
|
||||
<div class="my-5 modal position-static d-block" tabindex="-1" role="dialog" id="modalSignup">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content rounded-5 border-0">
|
||||
<div class="modal-header p-5 pb-4 border-bottom-0">
|
||||
<h2 class="fw-bold mb-0">Choisir un nouveau mot de passe</h2>
|
||||
</div>
|
||||
{# Message changement ok #}
|
||||
{% if message_changement is defined %}<div class="text-success font-weight-bold ps-5 pe-5">{{ message_changement }}</div>{% endif %}
|
||||
<div class="modal-body p-5 pt-0">
|
||||
{% include 'forms/user/change_password.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
{% include "footer.html.twig"%}
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
32
templates/user/forgotpassword.html.twig
Normal file
32
templates/user/forgotpassword.html.twig
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Mot de passe oublié{% endblock %}
|
||||
{% block body %}
|
||||
<head>
|
||||
<!-- Custom -->
|
||||
</head>
|
||||
<body>
|
||||
{% include "header.html.twig"%}
|
||||
<main>
|
||||
|
||||
<div class="my-5 modal position-static d-block" tabindex="-1" role="dialog" id="modalSignup">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content rounded-5 border-0">
|
||||
<div class="modal-header p-5 pb-4 border-bottom-0">
|
||||
<!-- <h5 class="modal-title">Modal title</h5> -->
|
||||
<h2 class="fw-bold mb-0">Mot de passe oublié</h2>
|
||||
</div>
|
||||
{# Message mail envoyé ou pas#}
|
||||
{% if message_password is defined %}<div class="text-success font-weight-bold ps-5 pe-5">{{ message_password }}</div>{% endif %}
|
||||
{% if message_mail_nok is defined %}<div class="text-danger font-weight-bold ps-5 pe-5">{{ message_mail_nok }}</div>{% endif %}
|
||||
<div class="modal-body p-5 pt-0">
|
||||
{% include 'forms/user/forgotpassword.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
{% include "footer.html.twig"%}
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
30
templates/user/login.html.twig
Executable file
30
templates/user/login.html.twig
Executable file
@@ -0,0 +1,30 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Connexion{% endblock %}
|
||||
{% block body %}
|
||||
<head>
|
||||
<!-- Custom -->
|
||||
</head>
|
||||
<body>
|
||||
{% include "header.html.twig"%}
|
||||
<main>
|
||||
|
||||
<div class="my-5 modal position-static d-block" tabindex="-1" role="dialog" id="modalSignup">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content rounded-5 border-0">
|
||||
<div class="modal-header p-5 pb-4 border-bottom-0">
|
||||
<!-- <h5 class="modal-title">Modal title</h5> -->
|
||||
<h2 class="fw-bold mb-0">Connexion</h2>
|
||||
</div>
|
||||
|
||||
<div class="modal-body p-5 pt-0">
|
||||
{% include 'forms/user/login.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
{% include "footer.html.twig"%}
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
75
templates/user/profile.html.twig
Normal file
75
templates/user/profile.html.twig
Normal file
@@ -0,0 +1,75 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}{{user.username}}{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<head>
|
||||
<!-- Custom -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% include 'header.html.twig' %}
|
||||
<main>
|
||||
<div class="d-flex flex-column justify-content-center align-items-center">
|
||||
<h2 class="fw-bold mt-3">Profil</h2>
|
||||
<form action="/profile/{{user.id}}" class="w-75" method="post" enctype="multipart/form-data">
|
||||
{# Username #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="username" type="text" placeholder="JohnDoe" value="{{user.username}}">
|
||||
<label for="username">Nom d'utilisateur</label>
|
||||
</div>
|
||||
{# Email #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="email" type="email" placeholder = "mail@example.com" value="{{user.email}}">
|
||||
<label for="username">Email</label>
|
||||
</div>
|
||||
{# Password #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control rounded-4" name="password" type="password" placeholder="mdp">
|
||||
<label for="password">Mot de passe</label>
|
||||
</div>
|
||||
{# Error message #}
|
||||
{% if message_register is defined %}<div class="text-danger font-weight-bold mt-3">{{ message_register }}</div>{% endif %}
|
||||
{# Password again #}
|
||||
<div class="form-floating mt-3">
|
||||
<input class="form-control" name="repassword" type="password" placeholder="mdp" >
|
||||
<label for="repassword">Confirmez le mot de passe</label>
|
||||
</div>
|
||||
{# Image #}
|
||||
<div class="form-group mt-3 w-50" style="float:left">
|
||||
<label for="image">Image</label>
|
||||
<input class="form-control" name="image" type="file">
|
||||
</div>
|
||||
<div class ="w-50 ps-4" style="float:right">
|
||||
<img src="{{user.image}}" alt="Image de profil" height="128" class="mt-3">
|
||||
</div>
|
||||
{# Submit #}
|
||||
<button class="mt-3 mb-3 btn btn-lg rounded-4 btn-primary" type="submit">Modifier</button>
|
||||
{# Delete #}
|
||||
<a class=" btn btn-lg ms-3 rounded-4 btn-danger" href="/delete/{{user.id}}" comfirm="Êtes vous sure de vouloir supprimer votre compte ?">Supprimer le compte</a
|
||||
</form>
|
||||
{# Message profil maj #}
|
||||
{% if message_profile is defined %}<div class="text-success font-weight-bold">{{ message_profile }}</div>{% endif %}
|
||||
</div>
|
||||
{% if user.google == "" %}
|
||||
<p class="text-center mt-3">AJOUTER GOOGLE AU COMPTE</p>
|
||||
<div id="g_id_onload"
|
||||
data-client_id="723424966880-015kn0qncavlgbj4j3k1ggs3arn7tdkd.apps.googleusercontent.com"
|
||||
data-login_uri="/auth/{{user.id}}"
|
||||
data-auto_prompt="false">
|
||||
</div>
|
||||
<div class="g_id_signin d-flex justify-content-center mb-2"
|
||||
data-type="standard"
|
||||
data-size="large"
|
||||
data-theme="filled_black"
|
||||
data-text="sign_in_with"
|
||||
data-shape="rectangular"
|
||||
data-logo_alignment="left">
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% include "footer.html.twig"%}
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
31
templates/user/register.html.twig
Normal file
31
templates/user/register.html.twig
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Inscription{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<head>
|
||||
<!-- Custom -->
|
||||
</head>
|
||||
<body>
|
||||
{% include "header.html.twig"%}
|
||||
<main>
|
||||
|
||||
<div class="my-5 modal position-static d-block" tabindex="-1" role="dialog" id="modalSignup">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content rounded-5 border-0">
|
||||
<div class="modal-header p-5 pb-4 border-bottom-0">
|
||||
<!-- <h5 class="modal-title">Modal title</h5> -->
|
||||
<h2 class="fw-bold mb-0">Inscription</h2>
|
||||
</div>
|
||||
|
||||
<div class="modal-body p-5 pt-0">
|
||||
{% include 'forms/user/register.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
{% include "footer.html.twig"%}
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
28
templates/user/register_google.html.twig
Normal file
28
templates/user/register_google.html.twig
Normal file
@@ -0,0 +1,28 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Inscription{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<head>
|
||||
<!-- Custom -->
|
||||
</head>
|
||||
<body>
|
||||
{% include "header.html.twig"%}
|
||||
<main>
|
||||
<div class="my-5 modal position-static d-block" tabindex="-1" role="dialog" id="modalSignup">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content rounded-5 border-0">
|
||||
<div class="modal-header p-5 pb-4 border-bottom-0">
|
||||
<h2 class="fw-bold mb-0">Inscription via Google</h2>
|
||||
<img src="/static/img/Google_logo.png" alt="Google_logo" height="50" >
|
||||
</div>
|
||||
<div class="modal-body p-5 pt-0">
|
||||
{% include 'forms/user/register_google.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% include "footer.html.twig"%}
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
37
templates/user/user_admin.html.twig
Normal file
37
templates/user/user_admin.html.twig
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Administration utilisateurs{% endblock %}
|
||||
{% block body %}
|
||||
<head>
|
||||
<!-- Custom -->
|
||||
<link href="/static/dashboard/dashboard.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
{% include 'header.html.twig' %}
|
||||
{# {% include 'forms/video' %} #}
|
||||
<div class="card m-4">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead class="text-uppercase">
|
||||
<tr>
|
||||
<th scope="col" style="max-width: 80px">Image</th>
|
||||
<th scope="col" class="w-100">Username<a class="btn btn-primary mdi mdi-account-plus" href="/add_user"></a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td><img src="{{user.image}}" alt="" style="max-width: 80px"></td>
|
||||
{% if user.password == "" %}
|
||||
<td>
|
||||
<button class="btn btn-primary mdi mdi-content-copy copy-input" input="#copyKey{{user.id}}"></button>
|
||||
<input type="text" value="{{uri}}/register/{{ user.username }}" id="copyKey{{user.id}}">
|
||||
</td>
|
||||
{% else %}
|
||||
<td>{{user.username}}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
15
templates/user/user_deleted.html.twig
Normal file
15
templates/user/user_deleted.html.twig
Normal file
@@ -0,0 +1,15 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}Compte supprimé{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<div class="text-center pt-5" style="height:100vh;">
|
||||
|
||||
<h1>Votre compte a bien été supprimé</h1>
|
||||
|
||||
<hr class="mx-5 mb-4">
|
||||
|
||||
<a href="/" class="btn btn-primary btn-lg mdi mdi-arrow-left"> Accueil</a>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user