mirror of
https://git.roussel.pro/telecom-paris/pact.git
synced 2026-02-09 02:20:17 +01:00
Merge branch 'quentin' of gitlab.enst.fr:pact/2022-2023/pact71 into main
This commit is contained in:
27
code/backend_reconnaissance/.dockerignore
Normal file
27
code/backend_reconnaissance/.dockerignore
Normal file
@@ -0,0 +1,27 @@
|
||||
**/__pycache__
|
||||
**/.venv
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
20
code/backend_reconnaissance/Dockerfile
Normal file
20
code/backend_reconnaissance/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM python:3.8-slim
|
||||
|
||||
#Ne pas créer les fichiers .pyc
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
#Afficher les logs directement dans le terminal
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
#Installation des dépendances de opencv
|
||||
RUN apt-get update
|
||||
RUN apt-get install ffmpeg libsm6 libxext6 -y
|
||||
|
||||
# Installation des dépendances python
|
||||
COPY requirements.txt .
|
||||
RUN python -m pip install -r requirements.txt
|
||||
|
||||
# Création du répertoire de travail
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
BIN
code/backend_reconnaissance/__pycache__/hands.cpython-310.pyc
Normal file
BIN
code/backend_reconnaissance/__pycache__/hands.cpython-310.pyc
Normal file
Binary file not shown.
@@ -1,29 +1,29 @@
|
||||
import requests
|
||||
#Exemple ajout d'un commentaire depuis la borne (site ou geste)
|
||||
avis = {
|
||||
"note": 8,
|
||||
"source": "borne",
|
||||
#Optionel
|
||||
"auteur_age": 20,
|
||||
"notes_autre": '{"proprete":8,"calme":10}',
|
||||
"auteur_sexe": 'f',
|
||||
"commentaire": "Commentaire"
|
||||
}
|
||||
|
||||
# res = requests.post("http://localhost:8080/add_review", data=avis)
|
||||
# print(res.text)
|
||||
|
||||
#Exemple ajout d'un commentaire trouvé sur les réseaux sociaux
|
||||
avis = {
|
||||
"auteur_nom": "michel",
|
||||
"source": "instagram",
|
||||
"note": 8,
|
||||
"date": "2022-12-24",
|
||||
#Optionel
|
||||
"commentaire": "J'ai beaucoup aimé !",
|
||||
"lien": "https://instagram.com/si_insta_avait_des_liens_vers_des_commentaires_faudrait_le_mettre_ici",
|
||||
"auteur_lien": "https://instagram.com/michel",
|
||||
}
|
||||
|
||||
res = requests.post("http://localhost:8080/add_social_review", data=avis)
|
||||
import requests
|
||||
#Exemple ajout d'un commentaire depuis la borne (site ou geste)
|
||||
avis = {
|
||||
"note": 8,
|
||||
"source": "borne",
|
||||
#Optionel
|
||||
"auteur_age": 20,
|
||||
"notes_autre": '{"proprete":8,"calme":10}',
|
||||
"auteur_sexe": 'f',
|
||||
"commentaire": "Commentaire"
|
||||
}
|
||||
|
||||
res = requests.post("http://localhost:8080/add_review", data=avis)
|
||||
# print(res.text)
|
||||
|
||||
#Exemple ajout d'un commentaire trouvé sur les réseaux sociaux
|
||||
avis = {
|
||||
"auteur_nom": "michel",
|
||||
"source": "instagram",
|
||||
"note": 8,
|
||||
"date": "2022-12-24",
|
||||
#Optionel
|
||||
"commentaire": "J'ai beaucoup aimé !",
|
||||
"lien": "https://instagram.com/si_insta_avait_des_liens_vers_des_commentaires_faudrait_le_mettre_ici",
|
||||
"auteur_lien": "https://instagram.com/michel",
|
||||
}
|
||||
|
||||
# res = requests.post("http://localhost:8080/add_social_review", data=avis)
|
||||
print(res.text)
|
||||
44
code/backend_reconnaissance/hands.py
Normal file
44
code/backend_reconnaissance/hands.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import cv2
|
||||
import mediapipe as mp
|
||||
mp_drawing = mp.solutions.drawing_utils
|
||||
mp_drawing_styles = mp.solutions.drawing_styles
|
||||
mp_hands = mp.solutions.hands
|
||||
|
||||
# For webcam input:
|
||||
cap = cv2.VideoCapture(0)
|
||||
hands = mp_hands.Hands(
|
||||
model_complexity=0,
|
||||
min_detection_confidence=0.5,
|
||||
min_tracking_confidence=0.5)
|
||||
|
||||
def frame():
|
||||
if cap.isOpened():
|
||||
success, image = cap.read()
|
||||
if not success:
|
||||
print("Ignoring empty camera frame.")
|
||||
# If loading a video, use 'break' instead of 'continue'.
|
||||
return
|
||||
|
||||
# To improve performance, optionally mark the image as not writeable to
|
||||
# pass by reference.
|
||||
image.flags.writeable = False
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
results = hands.process(image)
|
||||
|
||||
# Draw the hand annotations on the image.
|
||||
image.flags.writeable = True
|
||||
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
||||
if results.multi_hand_landmarks:
|
||||
for hand_landmarks in results.multi_hand_landmarks:
|
||||
mp_drawing.draw_landmarks(
|
||||
image,
|
||||
hand_landmarks,
|
||||
mp_hands.HAND_CONNECTIONS,
|
||||
mp_drawing_styles.get_default_hand_landmarks_style(),
|
||||
mp_drawing_styles.get_default_hand_connections_style())
|
||||
# Flip the image horizontally for a selfie-view display.
|
||||
# cv2.imshow('MediaPipe Hands', cv2.flip(image, 1))
|
||||
if cv2.waitKey(5) & 0xFF == 27:
|
||||
return
|
||||
# cap.release()
|
||||
|
||||
39
code/backend_reconnaissance/main.py
Normal file
39
code/backend_reconnaissance/main.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import asyncio
|
||||
import json
|
||||
import math
|
||||
import websockets
|
||||
import random
|
||||
import os
|
||||
import hands
|
||||
import time
|
||||
|
||||
values = []
|
||||
class WebsocketServer:
|
||||
def __init__(self,getEffects,port=os.getenv("PORT"),host=os.getenv("HOST")) -> None:
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.getEffects = getEffects
|
||||
|
||||
async def run(self):
|
||||
async with websockets.serve(self.handler, self.host, self.port):
|
||||
await asyncio.Future()
|
||||
|
||||
|
||||
async def handler(self,websocket):
|
||||
while True:
|
||||
start = time.time()
|
||||
messages = self.getEffects()
|
||||
hands.frame()
|
||||
await websocket.send(json.dumps(messages))
|
||||
# await asyncio.sleep(1/30)
|
||||
delay = time.time() - start
|
||||
values.append(1/delay)
|
||||
avg = sum(values) / len(values)
|
||||
dev = [(v - avg) ** 2 for v in values]
|
||||
print(avg, math.sqrt(sum(dev)/len(dev)))
|
||||
#Remplacer ça par la fonction qui récupère les effets (dans le module de reconnaissance de gestes)
|
||||
def getEffects():
|
||||
return {"type": "effects", "effects": [{"type": "thumbs_up", "x":random.randint(0,100), "y": random.randint(0,100), "width": 50, "height": 50}]}
|
||||
|
||||
server = WebsocketServer(getEffects)
|
||||
asyncio.run(server.run())
|
||||
4
code/backend_reconnaissance/requirements.txt
Normal file
4
code/backend_reconnaissance/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
websockets
|
||||
requests
|
||||
opencv-python
|
||||
mediapipe
|
||||
@@ -1,444 +1,425 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 4.9.5deb2
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- Host: localhost:3306
|
||||
-- Generation Time: Dec 26, 2022 at 10:31 AM
|
||||
-- Server version: 8.0.31-0ubuntu0.20.04.1
|
||||
-- PHP Version: 7.4.3
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET AUTOCOMMIT = 0;
|
||||
START TRANSACTION;
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- Database: `telereview`
|
||||
--
|
||||
CREATE DATABASE IF NOT EXISTS `telereview` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
USE `telereview`;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `borne_auteurs`
|
||||
--
|
||||
|
||||
CREATE TABLE `borne_auteurs` (
|
||||
`id` int NOT NULL,
|
||||
`sexe` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
|
||||
`age` tinyint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `borne_avis`
|
||||
--
|
||||
|
||||
CREATE TABLE `borne_avis` (
|
||||
`id` int NOT NULL,
|
||||
`id_auteur` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`note_principale` tinyint NOT NULL,
|
||||
`commentaire` text NOT NULL,
|
||||
`source_id` int NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `borne_criteres`
|
||||
--
|
||||
|
||||
CREATE TABLE `borne_criteres` (
|
||||
`id` int NOT NULL,
|
||||
`nom` text NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Dumping data for table `borne_criteres`
|
||||
--
|
||||
|
||||
INSERT INTO `borne_criteres` (`id`, `nom`) VALUES
|
||||
(1, 'proprete'),
|
||||
(2, 'calme'),
|
||||
(3, 'attente');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `borne_notes_autre`
|
||||
--
|
||||
|
||||
CREATE TABLE `borne_notes_autre` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`avis_id` int NOT NULL,
|
||||
`note` int NOT NULL COMMENT 'Note sur 10'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `reseaux_sociaux_auteurs`
|
||||
--
|
||||
|
||||
CREATE TABLE `reseaux_sociaux_auteurs` (
|
||||
`id` int NOT NULL,
|
||||
`nom_utilisateur` text NOT NULL,
|
||||
`source_id` int NOT NULL,
|
||||
`lien` text NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `reseaux_sociaux_avis`
|
||||
--
|
||||
|
||||
CREATE TABLE `reseaux_sociaux_avis` (
|
||||
`id` int NOT NULL,
|
||||
`date` date NOT NULL,
|
||||
`source_id` int NOT NULL,
|
||||
`note` tinyint DEFAULT NULL,
|
||||
`commentaire` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
|
||||
`auteur_id` int NOT NULL,
|
||||
`lien_source` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `sources`
|
||||
--
|
||||
|
||||
CREATE TABLE `sources` (
|
||||
`id` int NOT NULL,
|
||||
`nom` text NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Dumping data for table `sources`
|
||||
--
|
||||
|
||||
INSERT INTO `sources` (`id`, `nom`) VALUES
|
||||
(1, 'website'),
|
||||
(2, 'borne'),
|
||||
(3, 'instagram');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_autres_annee`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_autres_annee` (
|
||||
`id` int NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`note` float NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_autres_jour`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_autres_jour` (
|
||||
`id` int NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`note` float NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_autres_mois`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_autres_mois` (
|
||||
`id` int NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`note` float NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_autres_semaine`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_autres_semaine` (
|
||||
`id` int NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`note` float NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_general_annee`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_general_annee` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`moyenne_globale` float NOT NULL,
|
||||
`moyenne_site` float NOT NULL,
|
||||
`moyenne_borne` float NOT NULL,
|
||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_general_jour`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_general_jour` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`moyenne_globale` float DEFAULT NULL,
|
||||
`moyenne_site` float DEFAULT NULL,
|
||||
`moyenne_borne` float DEFAULT NULL,
|
||||
`dist_age` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'Distribution de l''age des auteurs',
|
||||
`dist_sexe` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'Distribution du sexe des auteurs'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_general_mois`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_general_mois` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`moyenne_globale` float NOT NULL,
|
||||
`moyenne_site` float NOT NULL,
|
||||
`moyenne_borne` float NOT NULL,
|
||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_general_semaine`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_general_semaine` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`moyenne_globale` float NOT NULL,
|
||||
`moyenne_site` float NOT NULL,
|
||||
`moyenne_borne` float NOT NULL,
|
||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Indexes for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Indexes for table `borne_auteurs`
|
||||
--
|
||||
ALTER TABLE `borne_auteurs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `borne_avis`
|
||||
--
|
||||
ALTER TABLE `borne_avis`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `borne_criteres`
|
||||
--
|
||||
ALTER TABLE `borne_criteres`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `borne_notes_autre`
|
||||
--
|
||||
ALTER TABLE `borne_notes_autre`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `reseaux_sociaux_auteurs`
|
||||
--
|
||||
ALTER TABLE `reseaux_sociaux_auteurs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `reseaux_sociaux_avis`
|
||||
--
|
||||
ALTER TABLE `reseaux_sociaux_avis`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `sources`
|
||||
--
|
||||
ALTER TABLE `sources`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_autres_annee`
|
||||
--
|
||||
ALTER TABLE `stats_autres_annee`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_autres_jour`
|
||||
--
|
||||
ALTER TABLE `stats_autres_jour`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_autres_mois`
|
||||
--
|
||||
ALTER TABLE `stats_autres_mois`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_autres_semaine`
|
||||
--
|
||||
ALTER TABLE `stats_autres_semaine`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_general_annee`
|
||||
--
|
||||
ALTER TABLE `stats_general_annee`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_general_jour`
|
||||
--
|
||||
ALTER TABLE `stats_general_jour`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_general_mois`
|
||||
--
|
||||
ALTER TABLE `stats_general_mois`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_general_semaine`
|
||||
--
|
||||
ALTER TABLE `stats_general_semaine`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `borne_auteurs`
|
||||
--
|
||||
ALTER TABLE `borne_auteurs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `borne_avis`
|
||||
--
|
||||
ALTER TABLE `borne_avis`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `borne_criteres`
|
||||
--
|
||||
ALTER TABLE `borne_criteres`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `borne_notes_autre`
|
||||
--
|
||||
ALTER TABLE `borne_notes_autre`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `reseaux_sociaux_auteurs`
|
||||
--
|
||||
ALTER TABLE `reseaux_sociaux_auteurs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `reseaux_sociaux_avis`
|
||||
--
|
||||
ALTER TABLE `reseaux_sociaux_avis`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `sources`
|
||||
--
|
||||
ALTER TABLE `sources`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_autres_annee`
|
||||
--
|
||||
ALTER TABLE `stats_autres_annee`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_autres_jour`
|
||||
--
|
||||
ALTER TABLE `stats_autres_jour`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_autres_mois`
|
||||
--
|
||||
ALTER TABLE `stats_autres_mois`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_autres_semaine`
|
||||
--
|
||||
ALTER TABLE `stats_autres_semaine`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_general_annee`
|
||||
--
|
||||
ALTER TABLE `stats_general_annee`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_general_jour`
|
||||
--
|
||||
ALTER TABLE `stats_general_jour`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_general_mois`
|
||||
--
|
||||
ALTER TABLE `stats_general_mois`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_general_semaine`
|
||||
--
|
||||
ALTER TABLE `stats_general_semaine`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
COMMIT;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET AUTOCOMMIT = 0;
|
||||
START TRANSACTION;
|
||||
|
||||
|
||||
--
|
||||
-- Database: `telereview`
|
||||
--
|
||||
CREATE DATABASE IF NOT EXISTS `telereview`;
|
||||
USE `telereview`;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `borne_auteurs`
|
||||
--
|
||||
|
||||
CREATE TABLE `borne_auteurs` (
|
||||
`id` int NOT NULL,
|
||||
`sexe` tinytext ,
|
||||
`age` tinyint DEFAULT NULL
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `borne_avis`
|
||||
--
|
||||
|
||||
CREATE TABLE `borne_avis` (
|
||||
`id` int NOT NULL,
|
||||
`id_auteur` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`note_principale` tinyint NOT NULL,
|
||||
`commentaire` text NOT NULL,
|
||||
`source_id` int NOT NULL
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `borne_criteres`
|
||||
--
|
||||
|
||||
CREATE TABLE `borne_criteres` (
|
||||
`id` int NOT NULL,
|
||||
`nom` text NOT NULL
|
||||
) ;
|
||||
|
||||
--
|
||||
-- Dumping data for table `borne_criteres`
|
||||
--
|
||||
|
||||
INSERT INTO `borne_criteres` (`id`, `nom`) VALUES
|
||||
(1, 'proprete'),
|
||||
(2, 'calme'),
|
||||
(3, 'attente');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `borne_notes_autre`
|
||||
--
|
||||
|
||||
CREATE TABLE `borne_notes_autre` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`avis_id` int NOT NULL,
|
||||
`note` int NOT NULL COMMENT 'Note sur 10'
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `reseaux_sociaux_auteurs`
|
||||
--
|
||||
|
||||
CREATE TABLE `reseaux_sociaux_auteurs` (
|
||||
`id` int NOT NULL,
|
||||
`nom_utilisateur` text NOT NULL,
|
||||
`source_id` int NOT NULL,
|
||||
`lien` text NOT NULL
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `reseaux_sociaux_avis`
|
||||
--
|
||||
|
||||
CREATE TABLE `reseaux_sociaux_avis` (
|
||||
`id` int NOT NULL,
|
||||
`date` date NOT NULL,
|
||||
`source_id` int NOT NULL,
|
||||
`note` tinyint DEFAULT NULL,
|
||||
`commentaire` text ,
|
||||
`auteur_id` int NOT NULL,
|
||||
`lien_source` text
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `sources`
|
||||
--
|
||||
|
||||
CREATE TABLE `sources` (
|
||||
`id` int NOT NULL,
|
||||
`nom` text NOT NULL
|
||||
) ;
|
||||
|
||||
--
|
||||
-- Dumping data for table `sources`
|
||||
--
|
||||
|
||||
INSERT INTO `sources` (`id`, `nom`) VALUES
|
||||
(1, 'website'),
|
||||
(2, 'borne'),
|
||||
(3, 'instagram');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_autres_annee`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_autres_annee` (
|
||||
`id` int NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`note` float NOT NULL
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_autres_jour`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_autres_jour` (
|
||||
`id` int NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`note` float NOT NULL
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_autres_mois`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_autres_mois` (
|
||||
`id` int NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`note` float NOT NULL
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_autres_semaine`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_autres_semaine` (
|
||||
`id` int NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`critere_id` int NOT NULL,
|
||||
`note` float NOT NULL
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_general_annee`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_general_annee` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`moyenne_globale` float NOT NULL,
|
||||
`moyenne_site` float NOT NULL,
|
||||
`moyenne_borne` float NOT NULL,
|
||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_general_jour`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_general_jour` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`moyenne_globale` float DEFAULT NULL,
|
||||
`moyenne_site` float DEFAULT NULL,
|
||||
`moyenne_borne` float DEFAULT NULL,
|
||||
`dist_age` text COMMENT 'Distribution de l''age des auteurs',
|
||||
`dist_sexe` text COMMENT 'Distribution du sexe des auteurs'
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_general_mois`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_general_mois` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`moyenne_globale` float NOT NULL,
|
||||
`moyenne_site` float NOT NULL,
|
||||
`moyenne_borne` float NOT NULL,
|
||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||
) ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `stats_general_semaine`
|
||||
--
|
||||
|
||||
CREATE TABLE `stats_general_semaine` (
|
||||
`id` int NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`moyenne_globale` float NOT NULL,
|
||||
`moyenne_site` float NOT NULL,
|
||||
`moyenne_borne` float NOT NULL,
|
||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||
) ;
|
||||
|
||||
--
|
||||
-- Indexes for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Indexes for table `borne_auteurs`
|
||||
--
|
||||
ALTER TABLE `borne_auteurs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `borne_avis`
|
||||
--
|
||||
ALTER TABLE `borne_avis`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `borne_criteres`
|
||||
--
|
||||
ALTER TABLE `borne_criteres`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `borne_notes_autre`
|
||||
--
|
||||
ALTER TABLE `borne_notes_autre`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `reseaux_sociaux_auteurs`
|
||||
--
|
||||
ALTER TABLE `reseaux_sociaux_auteurs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `reseaux_sociaux_avis`
|
||||
--
|
||||
ALTER TABLE `reseaux_sociaux_avis`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `sources`
|
||||
--
|
||||
ALTER TABLE `sources`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_autres_annee`
|
||||
--
|
||||
ALTER TABLE `stats_autres_annee`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_autres_jour`
|
||||
--
|
||||
ALTER TABLE `stats_autres_jour`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_autres_mois`
|
||||
--
|
||||
ALTER TABLE `stats_autres_mois`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_autres_semaine`
|
||||
--
|
||||
ALTER TABLE `stats_autres_semaine`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_general_annee`
|
||||
--
|
||||
ALTER TABLE `stats_general_annee`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_general_jour`
|
||||
--
|
||||
ALTER TABLE `stats_general_jour`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_general_mois`
|
||||
--
|
||||
ALTER TABLE `stats_general_mois`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `stats_general_semaine`
|
||||
--
|
||||
ALTER TABLE `stats_general_semaine`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `borne_auteurs`
|
||||
--
|
||||
ALTER TABLE `borne_auteurs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `borne_avis`
|
||||
--
|
||||
ALTER TABLE `borne_avis`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `borne_criteres`
|
||||
--
|
||||
ALTER TABLE `borne_criteres`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `borne_notes_autre`
|
||||
--
|
||||
ALTER TABLE `borne_notes_autre`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `reseaux_sociaux_auteurs`
|
||||
--
|
||||
ALTER TABLE `reseaux_sociaux_auteurs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `reseaux_sociaux_avis`
|
||||
--
|
||||
ALTER TABLE `reseaux_sociaux_avis`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `sources`
|
||||
--
|
||||
ALTER TABLE `sources`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_autres_annee`
|
||||
--
|
||||
ALTER TABLE `stats_autres_annee`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_autres_jour`
|
||||
--
|
||||
ALTER TABLE `stats_autres_jour`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_autres_mois`
|
||||
--
|
||||
ALTER TABLE `stats_autres_mois`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_autres_semaine`
|
||||
--
|
||||
ALTER TABLE `stats_autres_semaine`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_general_annee`
|
||||
--
|
||||
ALTER TABLE `stats_general_annee`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_general_jour`
|
||||
--
|
||||
ALTER TABLE `stats_general_jour`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_general_mois`
|
||||
--
|
||||
ALTER TABLE `stats_general_mois`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `stats_general_semaine`
|
||||
--
|
||||
ALTER TABLE `stats_general_semaine`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
COMMIT;
|
||||
98
code/docker-compose.yaml
Normal file
98
code/docker-compose.yaml
Normal file
@@ -0,0 +1,98 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
|
||||
#Base de donnée mysql de la borne sur laquelle est stockée tous les avis et les stats
|
||||
db:
|
||||
image: mysql:latest
|
||||
container_name: db
|
||||
expose:
|
||||
- 3306
|
||||
volumes:
|
||||
- ./db:/docker-entrypoint-initdb.d
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "-uroot"] # Command to check health.
|
||||
interval: 5s # Interval between health checks.
|
||||
timeout: 5s # Timeout for each health checking.
|
||||
retries: 20 # Hou many times retries.
|
||||
start_period: 10s # Estimated time to boot.
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: telereview
|
||||
MYSQL_DATABASE: telereview
|
||||
|
||||
#Interface d'aministration pour la bdd
|
||||
phpmyadmin:
|
||||
image: phpmyadmin:latest
|
||||
restart: always
|
||||
container_name: phpmyadmin
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
PMA_ARBITRARY: 1
|
||||
PMA_HOST: db
|
||||
PMA_USER: root
|
||||
PMA_PASSWORD: telereview
|
||||
ports:
|
||||
- 8000:80
|
||||
|
||||
#API de gestion des avis, permet d'ajouter ou de récuperer des avis ou les stats sur les avis par des requêtes HTTP
|
||||
reviews_api:
|
||||
container_name: reviews_api
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DB_USER=root
|
||||
- DB_PASSWORD=telereview
|
||||
- DB_HOST=db
|
||||
- DB_NAME=telereview
|
||||
- PORT=8080
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
build: ./reviews_api
|
||||
restart: always
|
||||
|
||||
#Serveur web de l'interface de la borne
|
||||
interface_borne:
|
||||
image: httpd:latest
|
||||
volumes:
|
||||
- ./interface_borne:/usr/local/apache2/htdocs/
|
||||
container_name: interface_borne
|
||||
ports:
|
||||
- 8888:80
|
||||
|
||||
#Serveur web de l'interface admin
|
||||
interface_admin:
|
||||
image: httpd:latest
|
||||
volumes:
|
||||
- ./interface_admin:/usr/local/apache2/htdocs/
|
||||
container_name: interface_admin
|
||||
ports:
|
||||
- 80:80
|
||||
|
||||
#Backend de la borne : scripts pythons de reconnaissances video et audio
|
||||
#Envoient les infos a l'interface de la borne par websocket pour mettre a jour l'interface rapidement
|
||||
#Met a jour les avis en faisant des requêtes a l'API
|
||||
backend_reconnaissance:
|
||||
build: ./backend_reconnaissance
|
||||
container_name: backend_reconnaissance
|
||||
restart: always
|
||||
devices:
|
||||
- /dev/video3:/dev/video0
|
||||
environment:
|
||||
- PORT=5000
|
||||
- HOST=backend_reconnaissance
|
||||
ports:
|
||||
#Ce container est le serveur websocker dont le client est l'interface de la borne qui tourne dans le navigateur
|
||||
- 5000:5000
|
||||
|
||||
video_loopback:
|
||||
build: ./video_loopback
|
||||
container_name: video_loopback
|
||||
restart: always
|
||||
devices:
|
||||
- /dev/video0:/dev/video0
|
||||
- /dev/video2:/dev/video1
|
||||
- /dev/video3:/dev/video2
|
||||
2
code/interface_admin/.gitignore
vendored
Normal file
2
code/interface_admin/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.next
|
||||
package-lock.json
|
||||
34
code/interface_admin/README.md
Normal file
34
code/interface_admin/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
58
code/interface_admin/components/Avis.jsx
Normal file
58
code/interface_admin/components/Avis.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react'
|
||||
import { Card, Col, Row, Table } from 'react-bootstrap';
|
||||
import { BsPersonFill } from 'react-icons/bs';
|
||||
import styles from '../styles/Avis.module.css'
|
||||
|
||||
export default function Avis({review}) {
|
||||
const {date, note_principale,notes_autres, commentaire, sexe_auteur, nom_source, age_auteur} = review;
|
||||
return (
|
||||
<Card>
|
||||
<Card.Title>Avis</Card.Title>
|
||||
<Card.Body>
|
||||
<Row>
|
||||
<h2>Auteur</h2>
|
||||
<Col xs={1}>
|
||||
<BsPersonFill className={styles.personIcon} />
|
||||
</Col>
|
||||
<Col className='d-flex flex-column'>
|
||||
<p>Age : {age_auteur}</p>
|
||||
<p>Sexe : {sexe_auteur}</p>
|
||||
<p>Date de publication : {date}</p>
|
||||
<p>Source : {nom_source}</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<h2>Notes</h2>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Critère</th>
|
||||
<th>Note</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Général</td>
|
||||
<td>{note_principale} / 10</td>
|
||||
</tr>
|
||||
{notes_autres && notes_autres.map(({ critere, note }) => {
|
||||
return <tr key={critere}>
|
||||
<td>{critere}</td>
|
||||
<td>{note}/10</td>
|
||||
</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Row>
|
||||
<Row>
|
||||
<Card>
|
||||
<Card.Header>Commentaire</Card.Header>
|
||||
<Card.Body>
|
||||
{commentaire}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Row>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
34
code/interface_admin/components/AvisList.jsx
Normal file
34
code/interface_admin/components/AvisList.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react'
|
||||
import { Table } from 'react-bootstrap'
|
||||
import styles from '../styles/AvisList.module.css'
|
||||
|
||||
export default function AvisList({ avis }) {
|
||||
const router = useRouter();
|
||||
function handleClick(id) {
|
||||
router.push(`/avis/${id}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Note globale</th>
|
||||
<th>Commentaire</th>
|
||||
<th>Source</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{avis.map(({ id, note_principale, commentaire, date, nom_source }) => {
|
||||
return <tr onClick={() => handleClick(id)} key={id} className={styles.row}>
|
||||
<td>{date}</td>
|
||||
<td>{note_principale} / 10</td>
|
||||
<td>{commentaire}</td>
|
||||
<td>{nom_source}</td>
|
||||
</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
47
code/interface_admin/components/ComparativeBarChart.jsx
Normal file
47
code/interface_admin/components/ComparativeBarChart.jsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react'
|
||||
import { Bar } from 'react-chartjs-2'
|
||||
import Chart from 'chart.js/auto';
|
||||
|
||||
export default function ComparativeBarChart({ xlabels, data0, label0, data1, label1}) {
|
||||
return (
|
||||
<Bar
|
||||
options={{
|
||||
// plugins: {
|
||||
// title: {
|
||||
// display: true,
|
||||
// text: title
|
||||
// },
|
||||
// },
|
||||
responsive: true,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true
|
||||
}
|
||||
}
|
||||
}}
|
||||
data={{
|
||||
labels: xlabels,
|
||||
datasets: [
|
||||
{
|
||||
label: label0,
|
||||
data: data0,
|
||||
backgroundColor: "#FF3B30",
|
||||
stack: "stack0"
|
||||
},
|
||||
{
|
||||
label: label1,
|
||||
data: data1,
|
||||
backgroundColor: "#0000FF",
|
||||
stack: "stack1"
|
||||
}
|
||||
]
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
23
code/interface_admin/components/Menu.jsx
Normal file
23
code/interface_admin/components/Menu.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Nav from 'react-bootstrap/Nav';
|
||||
import Navbar from 'react-bootstrap/Navbar';
|
||||
|
||||
export default function Menu() {
|
||||
return (
|
||||
<Navbar bg="light" expand="lg">
|
||||
<Container>
|
||||
<Navbar.Brand href="#home">Téléreview</Navbar.Brand>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
<Nav className="me-auto">
|
||||
<Link href="/" passHref legacyBehavior><Nav.Link>Accueil</Nav.Link></Link>
|
||||
<Link href="/stats" passHref legacyBehavior><Nav.Link>Statistiques</Nav.Link></Link>
|
||||
<Link href="/avis" passHref legacyBehavior><Nav.Link>Avis</Nav.Link></Link>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
)
|
||||
}
|
||||
3
code/interface_admin/config/reviewsApi.js
Normal file
3
code/interface_admin/config/reviewsApi.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export const api = {
|
||||
HOST: 'localhost:8080'
|
||||
}
|
||||
32
code/interface_admin/hooks/review.js
Normal file
32
code/interface_admin/hooks/review.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { api } from "../config/reviewsApi";
|
||||
|
||||
|
||||
function useReview(reviewId) {
|
||||
const [review, setReview] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
async function fetchData(id) {
|
||||
const response = await fetch('http://' + api.HOST + `/borne/get_review?id=${id}`)
|
||||
if (response.ok) {
|
||||
const jsonData = await response.json();
|
||||
setReview(jsonData);
|
||||
setLoading(false);
|
||||
setError(false);
|
||||
} else {
|
||||
setError(true);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (reviewId) {
|
||||
fetchData(reviewId);
|
||||
}
|
||||
}, [reviewId])
|
||||
|
||||
return { review, loading, error }
|
||||
}
|
||||
|
||||
export default useReview;
|
||||
30
code/interface_admin/hooks/reviews.js
Normal file
30
code/interface_admin/hooks/reviews.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { api } from "../config/reviewsApi";
|
||||
|
||||
export default function useReviews() {
|
||||
const [reviews, setReviews] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
async function fetchLastReviews(limit=100) {
|
||||
setLoading(true);
|
||||
const response = await fetch('http://' + api.HOST + '/borne/get_last_reviews', {
|
||||
method: 'GET'
|
||||
})
|
||||
if(response.ok) {
|
||||
let json = await response.json()
|
||||
setReviews(json);
|
||||
setError(false);
|
||||
setLoading(false);
|
||||
}else {
|
||||
setLoading(false);
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchLastReviews();
|
||||
}, [])
|
||||
|
||||
return {reviews, error, loading, fetchLastReviews};
|
||||
}
|
||||
26
code/interface_admin/hooks/stats.js
Normal file
26
code/interface_admin/hooks/stats.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useEffect } from "react";
|
||||
import { api } from "../config/reviewsApi";
|
||||
|
||||
export default function useStats(limit, interval) {
|
||||
const [stats, setStats] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
async function fetchData(limit, interval) {
|
||||
const response = await fetch("http://" + api.HOST + `/get_stats?interval=${interval}&limit=${limit}`)
|
||||
if(response.ok) {
|
||||
const data = await response.json();
|
||||
setStats(data);
|
||||
setError(false);
|
||||
}else {
|
||||
setError(true)
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(limit, interval);
|
||||
})
|
||||
|
||||
return {stats, loading, error};
|
||||
}
|
||||
6
code/interface_admin/next.config.js
Normal file
6
code/interface_admin/next.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
24
code/interface_admin/package.json
Normal file
24
code/interface_admin/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "interface-admin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/font": "13.1.6",
|
||||
"bootstrap": "^5.2.3",
|
||||
"chart.js": "^4.2.0",
|
||||
"eslint": "8.33.0",
|
||||
"eslint-config-next": "13.1.6",
|
||||
"next": "13.1.6",
|
||||
"react": "18.2.0",
|
||||
"react-bootstrap": "^2.7.0",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.7.1"
|
||||
}
|
||||
}
|
||||
13
code/interface_admin/pages/_app.js
Normal file
13
code/interface_admin/pages/_app.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Menu from '../components/Menu'
|
||||
import '../styles/globals.css'
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
import { Container } from 'react-bootstrap';
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return <>
|
||||
<Menu />
|
||||
<Container fluid="md">
|
||||
<Component {...pageProps} />
|
||||
</Container>
|
||||
</>
|
||||
}
|
||||
16
code/interface_admin/pages/_document.js
Normal file
16
code/interface_admin/pages/_document.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
12
code/interface_admin/pages/avis/[id].js
Normal file
12
code/interface_admin/pages/avis/[id].js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react'
|
||||
import Avis from '../../components/Avis';
|
||||
import useReview from '../../hooks/review';
|
||||
export default function AvisPage() {
|
||||
const router = useRouter();
|
||||
const {id} = router.query;
|
||||
const {review, loading, error} = useReview(id);
|
||||
return (
|
||||
!loading && !error && <Avis review={review}/>
|
||||
)
|
||||
}
|
||||
75
code/interface_admin/pages/avis/index.js
Normal file
75
code/interface_admin/pages/avis/index.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Card, Container, Form, Row, Table } from 'react-bootstrap'
|
||||
import AvisList from '../../components/AvisList';
|
||||
import useReviews from '../../hooks/reviews';
|
||||
import styles from '../../styles/AvisListPage.module.css'
|
||||
|
||||
export default function AvisListPage() {
|
||||
const [minGrade, setMinGrade] = useState(0);
|
||||
const [maxGrade, setMaxGrade] = useState(10);
|
||||
const [sources, setSources] = useState({'borne': true, 'website': true})
|
||||
const [filteredReviews, setFilteredREviews] = useState([])
|
||||
|
||||
const {reviews, error, loading} = useReviews();
|
||||
|
||||
useEffect(() => {
|
||||
console.log(reviews)
|
||||
const newReviews = reviews.filter((review) => review.note_principale >= minGrade && review.note_principale <= maxGrade && sources[review.nom_source])
|
||||
setFilteredREviews(newReviews)
|
||||
}, [reviews, minGrade, maxGrade, sources])
|
||||
|
||||
useEffect(() => {
|
||||
if(minGrade > maxGrade) {
|
||||
setMinGrade(maxGrade);
|
||||
}
|
||||
}, [maxGrade]);
|
||||
useEffect(() => {
|
||||
if(minGrade > maxGrade) {
|
||||
setMaxGrade(minGrade);
|
||||
}
|
||||
}, [minGrade])
|
||||
|
||||
|
||||
return (
|
||||
<Container fluid>
|
||||
<Card>
|
||||
<Card.Header>Tous les avis</Card.Header>
|
||||
<Card.Body>
|
||||
<Row>
|
||||
<Form>
|
||||
<Form.Group>
|
||||
<Form.Label>Types d'avis</Form.Label>
|
||||
<Form.Check
|
||||
type="switch"
|
||||
label="Borne"
|
||||
onChange={(e) => setSources({...sources, 'borne': e.target.checked})}
|
||||
checked={sources['borne']}
|
||||
/>
|
||||
<Form.Check
|
||||
type="switch"
|
||||
label="QR Code"
|
||||
onChange={(e) => setSources({...sources, 'website': e.target.checked})}
|
||||
checked={sources['website']}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Label>Note</Form.Label>
|
||||
<div className='d-flex flex-row justify-content-around col-md-6'>
|
||||
<div>Min : {minGrade}/10</div>
|
||||
<div className={styles.sliderContainer}>
|
||||
<input type="range" value={minGrade} onChange={(e) => setMinGrade(e.target.value)} min="0" max="10" step="1" className={styles.slider}></input>
|
||||
<input type="range" value={maxGrade} onChange={(e) => setMaxGrade(e.target.value)} min="0" max="10" step="1" className={styles.slider}></input>
|
||||
</div>
|
||||
<div>Max : {maxGrade}/10</div>
|
||||
</div>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Row>
|
||||
<Row>
|
||||
{!loading && !error && <AvisList avis={filteredReviews} />}
|
||||
</Row>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Container >
|
||||
)
|
||||
}
|
||||
76
code/interface_admin/pages/index.js
Normal file
76
code/interface_admin/pages/index.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import Head from 'next/head'
|
||||
import { Card, Container } from 'react-bootstrap'
|
||||
import ComparativeBarChart from '../components/ComparativeBarChart'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styles from "../styles/Home.module.css"
|
||||
|
||||
|
||||
export default function Home() {
|
||||
const [datasets, setDatasets] = useState(null);
|
||||
const [averages, setAverages] = useState(null);
|
||||
const [differences, setDifferences] = useState(null);
|
||||
useEffect(() => {
|
||||
if (datasets) {
|
||||
let newAverages = []
|
||||
let newDifferences = []
|
||||
for (let i = 0; i < datasets.length; i++) {
|
||||
newAverages[i] = datasets[i].current.reduce((a, b) => a + b) / datasets[i].current.length
|
||||
newDifferences[i] = newAverages[i] - datasets[i].previous.reduce((a, b) => a + b) / datasets[i].previous.length
|
||||
}
|
||||
setAverages(newAverages);
|
||||
setDifferences(newDifferences);
|
||||
}
|
||||
}, [datasets]);
|
||||
|
||||
useEffect(() => setDatasets([
|
||||
{ title: "Nombre d'avis", current: [3, 2, 3, 4, 5, 6, 7], previous: [7, 6, 5, 4, 3, 2, 1] },
|
||||
{ title: "Notes moyennes", current: [1, 2, 3, 4, 5, 6, 7], previous: [7, 6, 5, 4, 3, 2, 1] }
|
||||
]), []);
|
||||
function dataVisualizer(title, current, previous, average, difference) {
|
||||
return <div key={title}>
|
||||
<h3>{title}</h3>
|
||||
<Card className={styles.averageCard}>
|
||||
<Card.Title>Moyenne</Card.Title>
|
||||
<Card.Body className={styles.averageCardBody}>
|
||||
<div
|
||||
className={styles.averageMainValue}
|
||||
>
|
||||
{Math.round(average * 1e2) / 1e2}
|
||||
</div>
|
||||
<div
|
||||
className={[styles.averageCardSecondaryValue, difference >= 0 ? styles.averagePositive : styles.averageNegative].join(' ')}
|
||||
>
|
||||
{(difference >= 0 ? "+" : "-") + Math.round(difference * 1e2) / 1e2}
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<ComparativeBarChart
|
||||
xlabels={["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]}
|
||||
label0="Cette semaine"
|
||||
label1="La semaine dernière"
|
||||
data0={current}
|
||||
data1={previous}
|
||||
/>
|
||||
<hr />
|
||||
</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Create Next App</title>
|
||||
<meta name="description" content="Page d'accueil" />
|
||||
</Head>
|
||||
<Container fluid>
|
||||
<Card>
|
||||
<Card.Header as="h2">Vos performances cette semaine</Card.Header>
|
||||
<Card.Body>
|
||||
{datasets && averages && differences && datasets.map((set, i) => dataVisualizer(set.title, set.current, set.previous, averages[i], differences[i]))}
|
||||
</Card.Body>
|
||||
<div className='col col-12 col-lg-8 mx-auto'>
|
||||
</div>
|
||||
</Card>
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
7
code/interface_admin/pages/stats.js
Normal file
7
code/interface_admin/pages/stats.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Stats() {
|
||||
return (
|
||||
<div>stats</div>
|
||||
)
|
||||
}
|
||||
BIN
code/interface_admin/public/favicon.ico
Normal file
BIN
code/interface_admin/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
1
code/interface_admin/public/next.svg
Normal file
1
code/interface_admin/public/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
code/interface_admin/public/thirteen.svg
Normal file
1
code/interface_admin/public/thirteen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="31" fill="none"><g opacity=".9"><path fill="url(#a)" d="M13 .4v29.3H7V6.3h-.2L0 10.5V5L7.2.4H13Z"/><path fill="url(#b)" d="M28.8 30.1c-2.2 0-4-.3-5.7-1-1.7-.8-3-1.8-4-3.1a7.7 7.7 0 0 1-1.4-4.6h6.2c0 .8.3 1.4.7 2 .4.5 1 .9 1.7 1.2.7.3 1.6.4 2.5.4 1 0 1.7-.2 2.5-.5.7-.3 1.3-.8 1.7-1.4.4-.6.6-1.2.6-2s-.2-1.5-.7-2.1c-.4-.6-1-1-1.8-1.4-.8-.4-1.8-.5-2.9-.5h-2.7v-4.6h2.7a6 6 0 0 0 2.5-.5 4 4 0 0 0 1.7-1.3c.4-.6.6-1.3.6-2a3.5 3.5 0 0 0-2-3.3 5.6 5.6 0 0 0-4.5 0 4 4 0 0 0-1.7 1.2c-.4.6-.6 1.2-.6 2h-6c0-1.7.6-3.2 1.5-4.5 1-1.3 2.2-2.3 3.8-3C25 .4 26.8 0 28.8 0s3.8.4 5.3 1.1c1.5.7 2.7 1.7 3.6 3a7.2 7.2 0 0 1 1.2 4.2c0 1.6-.5 3-1.5 4a7 7 0 0 1-4 2.2v.2c2.2.3 3.8 1 5 2.2a6.4 6.4 0 0 1 1.6 4.6c0 1.7-.5 3.1-1.4 4.4a9.7 9.7 0 0 1-4 3.1c-1.7.8-3.7 1.1-5.8 1.1Z"/></g><defs><linearGradient id="a" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient><linearGradient id="b" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
code/interface_admin/public/vercel.svg
Normal file
1
code/interface_admin/public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
After Width: | Height: | Size: 629 B |
5
code/interface_admin/styles/Avis.module.css
Normal file
5
code/interface_admin/styles/Avis.module.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.personIcon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* font-size: 50px; */
|
||||
}
|
||||
6
code/interface_admin/styles/AvisList.module.css
Normal file
6
code/interface_admin/styles/AvisList.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
/* ==== TABLEAU ==== */
|
||||
.row:hover {
|
||||
cursor: pointer;
|
||||
background-color: #EEE;
|
||||
}
|
||||
57
code/interface_admin/styles/AvisListPage.module.css
Normal file
57
code/interface_admin/styles/AvisListPage.module.css
Normal file
@@ -0,0 +1,57 @@
|
||||
/* ==== SLIDER ==== */
|
||||
.sliderContainer {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.sliderContainer > input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
pointer-events: all;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 1px #C6C6C6;
|
||||
cursor: pointer;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.sliderContainer > input[type=range]::-moz-range-thumb {
|
||||
z-index: 99;
|
||||
pointer-events: all;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 1px #C6C6C6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sliderContainer > input[type=range]::-webkit-slider-thumb:hover {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.sliderContainer >input[type=range]::-webkit-slider-thumb:active {
|
||||
box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
||||
-webkit-box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
||||
}
|
||||
.sliderContainer > input[type=range]::-moz-range-thumb:hover {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.sliderContainer >input[type=range]::-moz-range-thumb:active {
|
||||
box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
||||
-webkit-box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
||||
}
|
||||
|
||||
.sliderContainer >input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
background-color: #C6C6C6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
29
code/interface_admin/styles/Home.module.css
Normal file
29
code/interface_admin/styles/Home.module.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.averageCard {
|
||||
width: min-content;
|
||||
margin: 0 auto;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.averageCardBody {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.averageMainValue {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.averageSecondaryValue {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.averagePositive {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.averageNegative {
|
||||
color: red;
|
||||
}
|
||||
0
code/interface_admin/styles/globals.css
Normal file
0
code/interface_admin/styles/globals.css
Normal file
7
code/interface_borne/assets/css/bootstrap-grid.min.css
vendored
Normal file
7
code/interface_borne/assets/css/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
27
code/interface_borne/assets/css/main.css
Normal file
27
code/interface_borne/assets/css/main.css
Normal file
@@ -0,0 +1,27 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#camera > video, #camera > canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#camera > video {
|
||||
z-index: 0;
|
||||
}
|
||||
#camera > canvas {
|
||||
z-index: 1;
|
||||
}
|
||||
BIN
code/interface_borne/assets/img/thumbs_down.png
Normal file
BIN
code/interface_borne/assets/img/thumbs_down.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
BIN
code/interface_borne/assets/img/thumbs_up.png
Normal file
BIN
code/interface_borne/assets/img/thumbs_up.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
11
code/interface_borne/assets/js/audio_page.js
Normal file
11
code/interface_borne/assets/js/audio_page.js
Normal file
@@ -0,0 +1,11 @@
|
||||
class AudioPage {
|
||||
constructor() {
|
||||
this.isEnabled = false;
|
||||
this.DOMElement = document.getElementById("audio");
|
||||
|
||||
}
|
||||
set enabled(isEnabled) {
|
||||
this.isEnabled = isEnabled;
|
||||
this.DOMElement.style.display = isEnabled ? "block" : "none";
|
||||
}
|
||||
}
|
||||
145
code/interface_borne/assets/js/camera_page.js
Normal file
145
code/interface_borne/assets/js/camera_page.js
Normal file
@@ -0,0 +1,145 @@
|
||||
class CameraPage {
|
||||
constructor() {
|
||||
|
||||
this.spinnerWeight = 10;
|
||||
this.spinnerColor = "#0F0FFF"
|
||||
|
||||
this.canvas = document.getElementById("overlay-canvas");
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
this.video = document.getElementById("camera-video");
|
||||
this.width;
|
||||
this.height; //calcule automatiquement en fonction de la largeur du flux vidéo
|
||||
this.videoWidth;
|
||||
this.videoHeight;
|
||||
this.streaming = false;
|
||||
this.activeEffects = [];
|
||||
this.images = {};
|
||||
this._startup();
|
||||
this._loadImages();
|
||||
this._enabled = false;
|
||||
this.DOMElement = document.getElementById("camera");
|
||||
}
|
||||
|
||||
set enabled(val) {
|
||||
this._enabled = val;
|
||||
this.DOMElement.style.display = val ? "block" : "none";
|
||||
if (val) {
|
||||
this._frame();
|
||||
this.video.play();
|
||||
}else {
|
||||
this.video.pause();
|
||||
}
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
_startup() {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ video: true, audio: false })
|
||||
.then((stream) => {
|
||||
this.video.srcObject = stream;
|
||||
this.video.play();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Erreur pendant la lecture de la camera: ${err}`);
|
||||
});
|
||||
|
||||
this.video.addEventListener(
|
||||
"canplay",
|
||||
(ev) => {
|
||||
if (!this.streaming) {
|
||||
//calcul de la taille de la vidéo en fonction de la taille de la fenêtre pour qu'elle soit toujours visible
|
||||
let aspectRatio = this.video.videoWidth / this.video.videoHeight;
|
||||
if (window.innerHeight * aspectRatio > window.innerWidth) {
|
||||
this.width = window.innerWidth;
|
||||
this.height = window.innerWidth / aspectRatio;
|
||||
} else {
|
||||
this.width = window.innerHeight * aspectRatio;
|
||||
this.height = window.innerHeight;
|
||||
}
|
||||
|
||||
this.videoHeight = this.video.videoHeight;
|
||||
this.videoWidth = this.video.videoWidth;
|
||||
|
||||
this.video.setAttribute("width", this.width);
|
||||
this.video.setAttribute("height", this.height);
|
||||
this.canvas.setAttribute("width", this.width);
|
||||
this.canvas.setAttribute("height", this.height);
|
||||
this.streaming = true;
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
_loadImages() {
|
||||
this.images.thumbsUp = new Image();
|
||||
this.images.thumbsUp.src = "assets/img/thumbs_up.png";
|
||||
this.images.thumbsDown = new Image();
|
||||
this.images.thumbsDown.src = "assets/img/thumbs_down.png";
|
||||
}
|
||||
|
||||
_frame() {
|
||||
if (this.streaming && this.enabled && this.width && this.height) {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
// this.ctx.drawImage(this.video, 0, 0, this.width, this.height);
|
||||
this._drawEffects();
|
||||
}
|
||||
if (this.enabled) {
|
||||
requestAnimationFrame(() => this._frame());
|
||||
}
|
||||
}
|
||||
|
||||
_scaleEffect(x, y, width, height) {
|
||||
let xScale = this.width / this.videoWidth;
|
||||
let yScale = this.height / this.videoHeight;
|
||||
return {
|
||||
x: x * xScale,
|
||||
y: y * yScale,
|
||||
width: width * xScale,
|
||||
height: height * yScale
|
||||
}
|
||||
}
|
||||
|
||||
_drawEffects() {
|
||||
for (let effect of this.activeEffects) {
|
||||
let { x, y, width, height } = this._scaleEffect(effect.x, effect.y, effect.width, effect.height);
|
||||
if (effect.type == "thumbs_down") {
|
||||
this._drawThumbsDown(x, y, width, height);
|
||||
}
|
||||
if (effect.type == "thumbs_up") {
|
||||
this._drawThumbsUp(x, y, width, height);
|
||||
}
|
||||
if (effect.type == "loading") {
|
||||
this._drawLoading(x, y, width, effect.progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_drawLoading(x, y, radius, progress) {
|
||||
this.ctx.lineWidth = this.spinnerWeight;
|
||||
this.ctx.strokeStyle = this.spinnerColor;
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(x, y, radius, 0, progress * 2 * Math.PI);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
_drawThumbsDown(x, y, width, height) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.drawImage(this.images.thumbsDown, x, y, width, height);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
_drawThumbsUp(x, y, width, height) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.drawImage(this.images.thumbsUp, x, y, width, height);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
setEffects(effects) {
|
||||
this.activeEffects = effects;
|
||||
}
|
||||
}
|
||||
5
code/interface_borne/assets/js/main.js
Normal file
5
code/interface_borne/assets/js/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
let stateManager;
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
stateManager = new StateManager();
|
||||
}, false);
|
||||
16
code/interface_borne/assets/js/network.js
Normal file
16
code/interface_borne/assets/js/network.js
Normal file
@@ -0,0 +1,16 @@
|
||||
class WebsocketClient {
|
||||
constructor(onNewEffects, onNewState) {
|
||||
this.socket = new WebSocket("ws://localhost:5000");
|
||||
this.socket.addEventListener("open", (event) => {
|
||||
this.socket.send("connected");
|
||||
});
|
||||
this.socket.addEventListener("message", (event) => {
|
||||
let msg = JSON.parse(event.data);
|
||||
if (msg.type == "effects") {
|
||||
onNewEffects(msg.effects);
|
||||
}else if(msg.type == "state") {
|
||||
onNewState(msg.state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
12
code/interface_borne/assets/js/sleeping_page.js
Normal file
12
code/interface_borne/assets/js/sleeping_page.js
Normal file
@@ -0,0 +1,12 @@
|
||||
class SleepingPage {
|
||||
constructor(onWakeUp) {
|
||||
this.onWakeUp = onWakeUp;
|
||||
this.isEnabled = false;
|
||||
this.DOMElement = document.getElementById("sleeping-page");
|
||||
|
||||
}
|
||||
set enabled(isEnabled) {
|
||||
this.isEnabled = isEnabled;
|
||||
this.DOMElement.style.display = isEnabled ? "block" : "none";
|
||||
}
|
||||
}
|
||||
49
code/interface_borne/assets/js/state_manager.js
Normal file
49
code/interface_borne/assets/js/state_manager.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const STATE = {
|
||||
sleeping: 0,
|
||||
video: 1,
|
||||
audio: 2,
|
||||
thankYou: 3,
|
||||
};
|
||||
|
||||
class StateManager {
|
||||
constructor() {
|
||||
this._state = STATE.sleeping;
|
||||
this._cameraPage = new CameraPage();
|
||||
this._sleepingPage = new SleepingPage();
|
||||
this._audioPage = new AudioPage();
|
||||
this._thankYouPage = new ThankYouPage();
|
||||
|
||||
this.wsClient = new WebsocketClient(
|
||||
(effects) => this._cameraPage.setEffects(effects),
|
||||
(state) => this.changeState(state)
|
||||
);
|
||||
|
||||
this._sleepingPage.enabled = true;
|
||||
this._cameraPage.enabled = false;
|
||||
this._audioPage.enabled = false;
|
||||
this._thankYouPage.enabled = false;
|
||||
//TODO: Remove qd implémenté dans le backend
|
||||
document.getElementById("sleeping-page-continue").onclick = () => this.setState(STATE.video);
|
||||
}
|
||||
|
||||
setState(newState) {
|
||||
console.log({current:this._state,new:newState})
|
||||
if(this._state == STATE.sleeping && newState == STATE.video) {
|
||||
this._cameraPage.enabled = true;
|
||||
this._sleepingPage.enabled = false;
|
||||
this._state = newState;
|
||||
}else if(this._state == STATE.video && newState == STATE.audio) {
|
||||
this._cameraPage.enabled = false;
|
||||
this._audioPage.enabled = true;
|
||||
this._state = newState;
|
||||
}else if(this._state == STATE.audio && newState == STATE.thankYou) {
|
||||
this._audioPage.enabled = false;
|
||||
this._thankYouPage.enabled = true;
|
||||
this._state = newState;
|
||||
}else if(this._state == STATE.thankYou && newState == STATE.sleeping) {
|
||||
this._thankYouPage.enabled = false;
|
||||
this._sleepingPage.enabled = true;
|
||||
this._state = newState;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
code/interface_borne/assets/js/thank_you_page.js
Normal file
11
code/interface_borne/assets/js/thank_you_page.js
Normal file
@@ -0,0 +1,11 @@
|
||||
class ThankYouPage {
|
||||
constructor() {
|
||||
this.isEnabled = false;
|
||||
this.DOMElement = document.getElementById("thank-you");
|
||||
}
|
||||
|
||||
set enabled(isEnabled) {
|
||||
this.isEnabled = isEnabled;
|
||||
this.DOMElement.style.display = isEnabled ? "block" : "none";
|
||||
}
|
||||
}
|
||||
41
code/interface_borne/index.html
Normal file
41
code/interface_borne/index.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="assets/css/main.css">
|
||||
<!-- <link rel="stylesheet" href="assets/css/bootstrap-grid.min.css"> -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
|
||||
<title>Téléreview</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="sleeping-page" class="page">
|
||||
<h1>Venez donner votre avis</h1>
|
||||
<!-- Boutton pour test -->
|
||||
<button id="sleeping-page-continue">Continuer</button>
|
||||
</div>
|
||||
<div id="camera">
|
||||
<canvas id="overlay-canvas"></canvas>
|
||||
<video id="camera-video"></video>
|
||||
</div>
|
||||
<div id="audio">
|
||||
<h1>Enregistrement audio blabal</h1>
|
||||
</div>
|
||||
<div id="thank-you">
|
||||
<h1>Merci pour votre avis !!!!!!!!!!</h1>
|
||||
</div>
|
||||
<script src="assets/js/camera_page.js"></script>
|
||||
<script src="assets/js/network.js"></script>
|
||||
<script src="assets/js/thank_you_page.js"></script>
|
||||
<script src="assets/js/audio_page.js"></script>
|
||||
<script src="assets/js/sleeping_page.js"></script>
|
||||
<script src="assets/js/state_manager.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
24
code/reviews_api/.dockerignore
Normal file
24
code/reviews_api/.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
15
code/reviews_api/Dockerfile
Normal file
15
code/reviews_api/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:lts-alpine
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
#installation des dépendances
|
||||
COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
|
||||
RUN npm install --production --silent && mv node_modules ../
|
||||
|
||||
#On copie le code source
|
||||
COPY . .
|
||||
#On change le propriétaire du dossier
|
||||
RUN chown -R node /usr/src/app
|
||||
#On change l'utilisateur
|
||||
USER node
|
||||
|
||||
CMD ["node", "index.js"]
|
||||
@@ -1,31 +1,35 @@
|
||||
# Installation
|
||||
* Pour faire fonctinoner le serveur sur vos machines il y a 3 choses a faire
|
||||
1. Installer node js : https://nodejs.org/en/download/
|
||||
2. Ouvrir un terminal et aller dans ce dossier (code/server) et tapper `npm install` pour installer les pacakges nécessaires
|
||||
3. copier le fichier `.env_template` et le nommer `.env` et remplir les variables (cela est fait pour le pas mettre les mots de passes sur le gitlab, faites attention de ne jamais commit le fichier .env !!)
|
||||
4. pour lancer le serveur faire `node index.js`
|
||||
|
||||
# Utilisation
|
||||
## Avis laissés sur la borne (hors réseaux sociaux)
|
||||
### Routes GET :
|
||||
- `/borne/get_last_reviews?limit=LIM` : renvoie les LIM derniers avis sur la borne
|
||||
- `/borne/get_review?id=ID` : renvoie les infos sur l'avis d'in ID
|
||||
- `/borne/get_criteres` : renvoie les criteres de notations valide pour les notes autres
|
||||
- `/borne/notes_autres?critere=CRIT&limit=LIM` : renvoie les LIM dernières notes sur le critère CRIT
|
||||
- `/borne/notes_autres?id=ID&limit=LIM` : renvoie toutes les notes spécifiques liées à l'avis ID
|
||||
|
||||
### Routes POST
|
||||
- `/add_review` : Ajoute une review et un auteur, paramètres POST :
|
||||
* [OBLIGATOIRE] `note` : note principale de la review entre 0 et 10 compris
|
||||
* [OBLIGATOIRE] `source` : nom de la source de l'avis, doit être `borne` ou `website` pour resp la borne et le site
|
||||
* `auteur_age` : age de l'auteur
|
||||
* `auteur_sexe` : sexe de l'auteur (valeurs valide 'f', 'h', 'a')
|
||||
* `commentaire` : Commentaire laissé avec l'avis
|
||||
* `notes autres` : sous la forme
|
||||
```json
|
||||
{
|
||||
"critere1": 8,
|
||||
"critere2": 2,
|
||||
"critere3": 0
|
||||
}
|
||||
```
|
||||
# Serveur de traitement des données
|
||||
Ce serveur s'occupe de fournir une API web pour pourvoir ajouter des avis sur la borne, réxupérer des avis, calculer et fournir les statistiques sur ces avis
|
||||
|
||||
# Installation (Si vous voulez le lancer hors du container docker)
|
||||
* Pour faire fonctinoner le serveur sur vos machines il y a 3 choses a faire
|
||||
1. Installer node js : https://nodejs.org/en/download/
|
||||
2. Ouvrir un terminal et aller dans ce dossier (code/server) et tapper `npm install` pour installer les pacakges nécessaires
|
||||
3. copier le fichier `.env_template` et le nommer `.env` et remplir les variables (cela est fait pour le pas mettre les mots de passes sur le gitlab, faites attention de ne jamais commit le fichier .env !!)
|
||||
4. pour lancer le serveur faire `node index.js`
|
||||
|
||||
# Utilisation
|
||||
## Avis laissés sur la borne (hors réseaux sociaux)
|
||||
### Routes GET :
|
||||
- `/borne/get_last_reviews?limit=LIM` : renvoie les LIM derniers avis sur la borne
|
||||
- `/borne/get_review?id=ID` : renvoie les infos sur l'avis d'in ID
|
||||
- `/borne/get_criteres` : renvoie les criteres de notations valide pour les notes autres
|
||||
- `/borne/notes_autres?critere=CRIT&limit=LIM` : renvoie les LIM dernières notes sur le critère CRIT
|
||||
- `/borne/notes_autres?id=ID&limit=LIM` : renvoie toutes les notes spécifiques liées à l'avis ID
|
||||
- `get_stats?interval=INTERVAL&limit=LIM`: interval est "jour", "mois", "annee", "semaine", l'interval de calcul de données demandé, LIM est le nombre des stats à récupérer
|
||||
|
||||
### Routes POST
|
||||
- `/add_review` : Ajoute une review et un auteur, paramètres POST :
|
||||
* [OBLIGATOIRE] `note` : note principale de la review entre 0 et 10 compris
|
||||
* [OBLIGATOIRE] `source` : nom de la source de l'avis, doit être `borne` ou `website` pour resp la borne et le site
|
||||
* `auteur_age` : age de l'auteur
|
||||
* `auteur_sexe` : sexe de l'auteur (valeurs valide 'f', 'h', 'a')
|
||||
* `commentaire` : Commentaire laissé avec l'avis
|
||||
* `notes autres` : sous la forme
|
||||
```json
|
||||
{
|
||||
"critere1": 8,
|
||||
"critere2": 2,
|
||||
"critere3": 0
|
||||
}
|
||||
```
|
||||
@@ -1,200 +1,207 @@
|
||||
import conn from '../database.js';
|
||||
|
||||
/**
|
||||
* Renvoie les derniers avis laissés sur la borne trié par ordre chronologque décroissant
|
||||
* @param {Number} limit Nombre d'avis a afficher
|
||||
* @returns Une liste d'objets de la forme {id: id de l'avis, date: date de l'avis, note_principale: note sur 10, commentaire: avis textuel, nom_source, sexe_auteur, age_auteur}
|
||||
*/
|
||||
const getLastReviews = (limit=10) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT borne_avis.id,date,note_principale,commentaire,sources.nom as nom_source, borne_auteurs.sexe as sexe_auteur, borne_auteurs.age as age_auteur
|
||||
FROM borne_avis
|
||||
JOIN sources ON sources.id = source_id
|
||||
JOIN borne_auteurs ON borne_auteurs.id = id_auteur
|
||||
ORDER BY borne_avis.id DESC LIMIT ?`;
|
||||
conn.query(sql, [limit], (err, res) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
}else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les informations sur un avis avec un ID spécifique
|
||||
* @param {Number} id Id de la review
|
||||
* @returns Un objet de la forme des objets dans la table borne_avis ayant l'id id s'il existe, renvoie une erreur sinon
|
||||
*/
|
||||
const getReviewFromId = (id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT * FROM borne_avis WHERE id = ? LIMIT 1`;
|
||||
conn.query(sql, [id], (err, res) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
}else {
|
||||
if(res.length != 1) {
|
||||
reject(new Error("Avis avec cet ID non trouvé"))
|
||||
}else {
|
||||
resolve(res[0])
|
||||
}
|
||||
if(res.length != 1) {
|
||||
reject(new Error("Avis avec cet ID non trouvé"))
|
||||
}else {
|
||||
resolve(res[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie tout les critères de notation valides
|
||||
* @returns Une liste d'objets de la forme {id,nom}
|
||||
*/
|
||||
const getCriteres = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT * FROM borne_criteres`;
|
||||
conn.query(sql, [limit], (err, res) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
}else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les "limit" notes les plus récentes laissées pour un critère spécifié
|
||||
* @param {String} critere Nom de critère
|
||||
* @param {Number} limit Nombre max de note a afficher
|
||||
* @returns une liste d'objets de la forme {id,date,critere,note,avis_id:id de l'avis lié a cette note}
|
||||
*/
|
||||
const getNotesAutresFromCritere = (critere,limit=10) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT borne_notes_autre.id as id,date,borne_criteres.nom as critere, note, avis_id
|
||||
FROM borne_notes_autre
|
||||
JOIN borne_criteres ON borne_criteres.id = critere_id
|
||||
WHERE borne_criteres.nom = ?
|
||||
ORDER BY borne_notes_autre.id DESC LIMIT ? ;`;
|
||||
conn.query(sql, [critere,limit], (err, res) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
}else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie toutes les notes sur des critères spécifiques laissée pour un avis spécifique
|
||||
* @param {Number} reviewId Id de l'avis
|
||||
* @returns une liste d'objets de la forme {id,critere:nom du critère, note:note sur 10}
|
||||
*/
|
||||
const getNotesAutresFromReview = (reviewId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT borne_notes_autre.id as id,borne_criteres.nom as critere, note
|
||||
FROM borne_notes_autre
|
||||
JOIN borne_criteres on borne_criteres.id = critere_id
|
||||
WHERE avis_id = ?
|
||||
ORDER BY borne_notes_autre.id DESC`;
|
||||
conn.query(sql, [reviewId], (err, res) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
}else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les dernières statistiques
|
||||
* @param {String} interval Interval de temps de la statistique, valeurs possibles : "jour","mois","semaine","annee"
|
||||
* @param {Number} limit Nombre max de stats a renvoyer
|
||||
* @returns Une liste d'objet ou chaque objet correspond a une stat sur une periode donnée (par ex une stat hebdo datée du 07/01 correspond a une stat sur la semaine du 01/01 au 07/01), ces objets sont de la forme
|
||||
*/
|
||||
const getStats = (interval, limit=10) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!["jour","mois","semaine","annee"].includes(interval)) {
|
||||
reject(new Error("Invalid time interval"));
|
||||
return;
|
||||
}
|
||||
let sql = `SELECT * FROM stats_general_${interval} ORDER BY id DESC LIMIT ?;`;
|
||||
conn.query(sql, [limit], (err, res) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
}else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
Ces fonction sont des handlers pour les routes express, elles sont appelées par les routes et renvoient les données au format JSON
|
||||
*/
|
||||
export const handleGetLastReviews = (req, res) => {
|
||||
getLastReviews(req.query.limit)
|
||||
.then((reviews) => {
|
||||
res.send(reviews);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
export const handleGetReview = (req, res) => {
|
||||
getReviewFromId(req.query.id)
|
||||
.then((review) => {
|
||||
res.send(review);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
export const handleGetCriteres = (req, res) => {
|
||||
getCriteres()
|
||||
.then((criteres) => {
|
||||
res.send(criteres);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
export const handleGetNotesAutres = (req, res) => {
|
||||
if(req.query.critere) {
|
||||
getNotesAutresFromCritere(req.query.critere, req.query.limit)
|
||||
.then((notes) => {
|
||||
res.send(notes);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}else if(req.query.id) {
|
||||
getNotesAutresFromReview(req.query.id)
|
||||
.then((notes) => {
|
||||
res.send(notes);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}else {
|
||||
res.status(500).send("Error: no critere or id specified");
|
||||
}
|
||||
}
|
||||
|
||||
export const handleGetStats = (req, res) => {
|
||||
getStats(req.query.interval, req.query.limit)
|
||||
.then((stats) => {
|
||||
res.send(stats);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
import conn from '../database.js';
|
||||
|
||||
/**
|
||||
* Renvoie les derniers avis laissés sur la borne trié par ordre chronologque décroissant
|
||||
* @param {Number} limit Nombre d'avis a afficher
|
||||
* @returns Une liste d'objets de la forme {id: id de l'avis, date: date de l'avis, note_principale: note sur 10, commentaire: avis textuel, nom_source, sexe_auteur, age_auteur}
|
||||
*/
|
||||
const getLastReviews = (limit = 10) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT borne_avis.id,date,note_principale,commentaire,sources.nom as nom_source, borne_auteurs.sexe as sexe_auteur, borne_auteurs.age as age_auteur
|
||||
FROM borne_avis
|
||||
JOIN sources ON sources.id = source_id
|
||||
JOIN borne_auteurs ON borne_auteurs.id = id_auteur
|
||||
ORDER BY borne_avis.id DESC LIMIT ?`;
|
||||
conn.query(sql, [limit], (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les informations sur un avis avec un ID spécifique
|
||||
* @param {Number} id Id de la review
|
||||
* @returns Un objet de la forme des objets dans la table borne_avis ayant l'id id s'il existe, renvoie une erreur sinon
|
||||
*/
|
||||
const getReviewFromId = (id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT borne_avis.id,date,note_principale,commentaire,sources.nom as nom_source, borne_auteurs.sexe as sexe_auteur, borne_auteurs.age as age_auteur
|
||||
FROM borne_avis
|
||||
JOIN sources ON sources.id = source_id
|
||||
JOIN borne_auteurs ON borne_auteurs.id = id_auteur
|
||||
WHERE borne_avis.id = ?
|
||||
LIMIT 1`;
|
||||
conn.query(sql, [id], (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (res.length != 1) {
|
||||
reject(new Error("Avis avec cet ID non trouvé"))
|
||||
} else {
|
||||
resolve(res[0])
|
||||
}
|
||||
if (res.length != 1) {
|
||||
reject(new Error("Avis avec cet ID non trouvé"))
|
||||
} else {
|
||||
resolve(res[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie tout les critères de notation valides
|
||||
* @returns Une liste d'objets de la forme {id,nom}
|
||||
*/
|
||||
const getCriteres = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT * FROM borne_criteres`;
|
||||
conn.query(sql, [limit], (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les "limit" notes les plus récentes laissées pour un critère spécifié
|
||||
* @param {String} critere Nom de critère
|
||||
* @param {Number} limit Nombre max de note a afficher
|
||||
* @returns une liste d'objets de la forme {id,date,critere,note,avis_id:id de l'avis lié a cette note}
|
||||
*/
|
||||
const getNotesAutresFromCritere = (critere, limit = 10) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT borne_notes_autre.id as id,date,borne_criteres.nom as critere, note, avis_id
|
||||
FROM borne_notes_autre
|
||||
JOIN borne_criteres ON borne_criteres.id = critere_id
|
||||
WHERE borne_criteres.nom = ?
|
||||
ORDER BY borne_notes_autre.id DESC LIMIT ? ;`;
|
||||
conn.query(sql, [critere, limit], (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie toutes les notes sur des critères spécifiques laissée pour un avis spécifique
|
||||
* @param {Number} reviewId Id de l'avis
|
||||
* @returns une liste d'objets de la forme {id,critere:nom du critère, note:note sur 10}
|
||||
*/
|
||||
const getNotesAutresFromReview = (reviewId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = `SELECT borne_notes_autre.id as id,borne_criteres.nom as critere, note
|
||||
FROM borne_notes_autre
|
||||
JOIN borne_criteres on borne_criteres.id = critere_id
|
||||
WHERE avis_id = ?
|
||||
ORDER BY borne_notes_autre.id DESC`;
|
||||
conn.query(sql, [reviewId], (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les dernières statistiques
|
||||
* @param {String} interval Interval de temps de la statistique, valeurs possibles : "jour","mois","semaine","annee"
|
||||
* @param {Number} limit Nombre max de stats a renvoyer
|
||||
* @returns Une liste d'objet ou chaque objet correspond a une stat sur une periode donnée (par ex une stat hebdo datée du 07/01 correspond a une stat sur la semaine du 01/01 au 07/01), ces objets sont de la forme
|
||||
*/
|
||||
const getStats = (interval, limit = 10) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!["jour", "mois", "semaine", "annee"].includes(interval)) {
|
||||
reject(new Error("Invalid time interval"));
|
||||
return;
|
||||
}
|
||||
let sql = `SELECT * FROM stats_general_${interval} ORDER BY id DESC LIMIT ?;`;
|
||||
conn.query(sql, [limit], (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
Ces fonction sont des handlers pour les routes express, elles sont appelées par les routes et renvoient les données au format JSON
|
||||
*/
|
||||
export const handleGetLastReviews = (req, res) => {
|
||||
getLastReviews(req.query.limit)
|
||||
.then((reviews) => {
|
||||
res.send(reviews);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
export const handleGetReview = (req, res) => {
|
||||
getReviewFromId(req.query.id)
|
||||
.then((review) => {
|
||||
getNotesAutresFromReview(req.query.id).then((notesAutres) => {
|
||||
res.send({ ...review, notes_autres: notesAutres });
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
export const handleGetCriteres = (req, res) => {
|
||||
getCriteres()
|
||||
.then((criteres) => {
|
||||
res.send(criteres);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
export const handleGetNotesAutres = (req, res) => {
|
||||
if (req.query.critere) {
|
||||
getNotesAutresFromCritere(req.query.critere, req.query.limit)
|
||||
.then((notes) => {
|
||||
res.send(notes);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
} else if (req.query.id) {
|
||||
getNotesAutresFromReview(req.query.id)
|
||||
.then((notes) => {
|
||||
res.send(notes);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
} else {
|
||||
res.status(500).send("Error: no critere or id specified");
|
||||
}
|
||||
}
|
||||
|
||||
export const handleGetStats = (req, res) => {
|
||||
getStats(req.query.interval, req.query.limit)
|
||||
.then((stats) => {
|
||||
res.send(stats);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send("Error: " + err.message);
|
||||
});
|
||||
}
|
||||
@@ -1,84 +1,84 @@
|
||||
import { Auteur, Review } from './structures.js';
|
||||
import conn from '../database.js';
|
||||
import {getSourceId} from '../utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Ajoute un nouvel auteur de commentaire a la BDD
|
||||
* @param {Auteur} author L'auteur a ajouter
|
||||
* @returns une Promise qui renvoie l'id de l'utilisateur
|
||||
*/
|
||||
const addAuteur = (author) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
const sql = "INSERT INTO borne_auteurs (age, sexe) VALUES (?);"
|
||||
conn.query(sql, [[author.age, author.sexe]], (err, res) => {
|
||||
if(err) {
|
||||
reject(err)
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//Ajoute une note sur un critère spécifique dans la BDD
|
||||
const addSpecificRating = (reviewId, label, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = "INSERT INTO borne_notes_autre(critere_id, avis_id, note) VALUES ((SELECT id FROM borne_criteres WHERE borne_criteres.nom = ?), ?, ?)"
|
||||
conn.query(sql, [label,reviewId, value], (err, res) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ajoute un avis a la base de donnée
|
||||
* @param {Review} review la review a ajouter
|
||||
* @param {Number} authorId l'ID de l'auteur de l'avis dans la BDD
|
||||
* @param {Number} sourceId l'ID de la source de l'avis dans la BDD
|
||||
* @returns une Promise qui renvoie l'id de l'avis
|
||||
*/
|
||||
const addReview = (review, authorId, sourceId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = "INSERT INTO borne_avis (id_auteur, note_principale, commentaire, source_id) VALUES (?);"
|
||||
conn.query(sql, [[authorId, review.note, review.commentaire, sourceId]], (err, res) => {
|
||||
if(err) {
|
||||
reject(err)
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite une requête POST qui contient les valeurs nécessaires pour ajouter un nouvel avis dans la BDD
|
||||
* Essaie d'ajouter l'avis et l'auteur dans la BDD, sinon renvoie l'erreur avec un code 500
|
||||
* @param {*} req requete
|
||||
* @param {*} res reponse
|
||||
*/
|
||||
export const addReviewFromRequest = async (req,res) => {
|
||||
try {
|
||||
let notes_autre = {}
|
||||
try{
|
||||
notes_autre = JSON.parse(req.body.notes_autre);
|
||||
}catch(err){};
|
||||
|
||||
const author = new Auteur(req.body.auteur_age,req.body.auteur_sexe);
|
||||
const review = new Review(author, req.body.note, req.body.source, req.body.commentaire, notes_autre)
|
||||
let authorId = await addAuteur(author);
|
||||
let sourceId = await getSourceId(review.source);
|
||||
let reviewId = await addReview(review, authorId, sourceId );
|
||||
for(let label in review.notesAutre) {
|
||||
await addSpecificRating(reviewId, label, review.notesAutre[label]);
|
||||
}
|
||||
res.send("success")
|
||||
}catch(err) {
|
||||
res.status(500).send("Error : " + err.message)
|
||||
}
|
||||
import { Auteur, Review } from './structures.js';
|
||||
import conn from '../database.js';
|
||||
import {getSourceId} from '../utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Ajoute un nouvel auteur de commentaire a la BDD
|
||||
* @param {Auteur} author L'auteur a ajouter
|
||||
* @returns une Promise qui renvoie l'id de l'utilisateur
|
||||
*/
|
||||
const addAuteur = (author) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
const sql = "INSERT INTO borne_auteurs (age, sexe) VALUES (?);"
|
||||
conn.query(sql, [[author.age, author.sexe]], (err, res) => {
|
||||
if(err) {
|
||||
reject(err)
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//Ajoute une note sur un critère spécifique dans la BDD
|
||||
const addSpecificRating = (reviewId, label, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = "INSERT INTO borne_notes_autre(critere_id, avis_id, note) VALUES ((SELECT id FROM borne_criteres WHERE borne_criteres.nom = ?), ?, ?)"
|
||||
conn.query(sql, [label,reviewId, value], (err, res) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ajoute un avis a la base de donnée
|
||||
* @param {Review} review la review a ajouter
|
||||
* @param {Number} authorId l'ID de l'auteur de l'avis dans la BDD
|
||||
* @param {Number} sourceId l'ID de la source de l'avis dans la BDD
|
||||
* @returns une Promise qui renvoie l'id de l'avis
|
||||
*/
|
||||
const addReview = (review, authorId, sourceId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = "INSERT INTO borne_avis (id_auteur, note_principale, commentaire, source_id) VALUES (?);"
|
||||
conn.query(sql, [[authorId, review.note, review.commentaire, sourceId]], (err, res) => {
|
||||
if(err) {
|
||||
reject(err)
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite une requête POST qui contient les valeurs nécessaires pour ajouter un nouvel avis dans la BDD
|
||||
* Essaie d'ajouter l'avis et l'auteur dans la BDD, sinon renvoie l'erreur avec un code 500
|
||||
* @param {*} req requete
|
||||
* @param {*} res reponse
|
||||
*/
|
||||
export const addReviewFromRequest = async (req,res) => {
|
||||
try {
|
||||
let notes_autre = {}
|
||||
try{
|
||||
notes_autre = JSON.parse(req.body.notes_autre);
|
||||
}catch(err){};
|
||||
|
||||
const author = new Auteur(req.body.auteur_age,req.body.auteur_sexe);
|
||||
const review = new Review(author, req.body.note, req.body.source, req.body.commentaire, notes_autre)
|
||||
let authorId = await addAuteur(author);
|
||||
let sourceId = await getSourceId(review.source);
|
||||
let reviewId = await addReview(review, authorId, sourceId );
|
||||
for(let label in review.notesAutre) {
|
||||
await addSpecificRating(reviewId, label, review.notesAutre[label]);
|
||||
}
|
||||
res.send("success")
|
||||
}catch(err) {
|
||||
res.status(500).send("Error : " + err.message)
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,52 @@
|
||||
//Liste des valeurs valides pour le sexe d'un utilisateur
|
||||
const validSexes = ["h","f","a"];
|
||||
|
||||
//Classe pour représenter un avis laissé sur la borne que ce soit par l'interface web ou par un geste
|
||||
export class Review {
|
||||
/**
|
||||
* Constructeur
|
||||
* @param {Auteur} auteur L'auteur de l'avis
|
||||
* @param {Number} note La note principale entre 0 et 10 compris
|
||||
* @param {String} source La source de l'avis, doit être une entrée dans la table soruces de la bdd
|
||||
* @param {String} commentaire Le commentaire lié a l'avis
|
||||
* @param {Object} notesAutre Les notes secondaires laissées, sous la forme d'un object ou les clés sont les labels de chaque notes et les valeurs sont des notes entre 0 et 10 compris
|
||||
*/
|
||||
constructor(auteur, note, source, commentaire=null, notesAutre={}) {
|
||||
this.auteur = auteur;
|
||||
this.note = note;
|
||||
this.source = source;
|
||||
this.commentaire = commentaire;
|
||||
this.notesAutre = notesAutre;
|
||||
|
||||
//On vérifie si toutes les données sont correctes
|
||||
if(note < 0 || note > 10) {
|
||||
throw new Error("Note principale invalide");
|
||||
}
|
||||
for(let nom in notesAutre) {
|
||||
if(notesAutre[nom] < 0 || notesAutre[nom] > 10) {
|
||||
throw new Error("Note " + notesAutre[nom] +"/10 invalide");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Classe qui représente l'auteur d'un avis
|
||||
export class Auteur {
|
||||
/**
|
||||
* Constructeur
|
||||
* @param {Number} age L'age de l'auteur
|
||||
* @param {String} sexe Le sexe de la personne (doit être dans l'array validSexes défini au début de ce fichier)
|
||||
*/
|
||||
constructor(age=null, sexe=null) {
|
||||
this.age = age;
|
||||
this.sexe = sexe;
|
||||
|
||||
//Verification des données
|
||||
if(sexe != undefined && !validSexes.includes(sexe) ) {
|
||||
throw new Error("Sexe invalide");
|
||||
}
|
||||
if(age != undefined && age <= 0) {
|
||||
throw new Error("Age invalide");
|
||||
}
|
||||
}
|
||||
//Liste des valeurs valides pour le sexe d'un utilisateur
|
||||
const validSexes = ["h","f","a"];
|
||||
|
||||
//Classe pour représenter un avis laissé sur la borne que ce soit par l'interface web ou par un geste
|
||||
export class Review {
|
||||
/**
|
||||
* Constructeur
|
||||
* @param {Auteur} auteur L'auteur de l'avis
|
||||
* @param {Number} note La note principale entre 0 et 10 compris
|
||||
* @param {String} source La source de l'avis, doit être une entrée dans la table soruces de la bdd
|
||||
* @param {String} commentaire Le commentaire lié a l'avis
|
||||
* @param {Object} notesAutre Les notes secondaires laissées, sous la forme d'un object ou les clés sont les labels de chaque notes et les valeurs sont des notes entre 0 et 10 compris
|
||||
*/
|
||||
constructor(auteur, note, source, commentaire=null, notesAutre={}) {
|
||||
this.auteur = auteur;
|
||||
this.note = note;
|
||||
this.source = source;
|
||||
this.commentaire = commentaire;
|
||||
this.notesAutre = notesAutre;
|
||||
|
||||
//On vérifie si toutes les données sont correctes
|
||||
if(note < 0 || note > 10) {
|
||||
throw new Error("Note principale invalide");
|
||||
}
|
||||
for(let nom in notesAutre) {
|
||||
if(notesAutre[nom] < 0 || notesAutre[nom] > 10) {
|
||||
throw new Error("Note " + notesAutre[nom] +"/10 invalide");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Classe qui représente l'auteur d'un avis
|
||||
export class Auteur {
|
||||
/**
|
||||
* Constructeur
|
||||
* @param {Number} age L'age de l'auteur
|
||||
* @param {String} sexe Le sexe de la personne (doit être dans l'array validSexes défini au début de ce fichier)
|
||||
*/
|
||||
constructor(age=null, sexe=null) {
|
||||
this.age = age;
|
||||
this.sexe = sexe;
|
||||
|
||||
//Verification des données
|
||||
if(sexe != undefined && !validSexes.includes(sexe) ) {
|
||||
throw new Error("Sexe invalide");
|
||||
}
|
||||
if(age != undefined && age <= 0) {
|
||||
throw new Error("Age invalide");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as dotenv from 'dotenv'
|
||||
import mysql from 'mysql2';
|
||||
dotenv.config();
|
||||
|
||||
const conn = mysql.createConnection({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
multipleStatements: true,
|
||||
});
|
||||
|
||||
conn.connect();
|
||||
|
||||
import * as dotenv from 'dotenv'
|
||||
import mysql from 'mysql2';
|
||||
dotenv.config();
|
||||
|
||||
const conn = mysql.createConnection({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
multipleStatements: true,
|
||||
});
|
||||
|
||||
conn.connect();
|
||||
|
||||
export default conn;
|
||||
@@ -1,28 +1,29 @@
|
||||
import * as dotenv from 'dotenv';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import { addReviewFromRequest } from './borne/post_handler.js';
|
||||
import { addSocialReviewFromRequest } from './reseaux_sociaux/post_handler.js';
|
||||
import { startCronJobs } from './stats/update_stats.js';
|
||||
import * as borneHandler from './borne/get_handler.js';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(bodyParser.urlencoded({extended:true}))
|
||||
dotenv.config()
|
||||
app.post('/add_review', (req,res) => addReviewFromRequest(req,res));
|
||||
app.post('/add_social_review', (req,res) => addSocialReviewFromRequest(req,res));
|
||||
|
||||
app.get('/borne/get_last_reviews', borneHandler.handleGetLastReviews);
|
||||
app.get('/borne/get_review', borneHandler.handleGetReview);
|
||||
app.get('/borne/get_criteres', borneHandler.handleGetCriteres);
|
||||
app.get('/borne/notes_autres', borneHandler.handleGetNotesAutres);
|
||||
app.get('/borne/get_stats', borneHandler.handleGetStats);
|
||||
|
||||
|
||||
startCronJobs();
|
||||
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log("Server démaré sur le port " + process.env.PORT)
|
||||
})
|
||||
|
||||
import * as dotenv from 'dotenv';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import { addReviewFromRequest } from './borne/post_handler.js';
|
||||
import { addSocialReviewFromRequest } from './reseaux_sociaux/post_handler.js';
|
||||
import { startCronJobs } from './stats/update_stats.js';
|
||||
import * as borneHandler from './borne/get_handler.js';
|
||||
import cors from "cors";
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.urlencoded({extended:true}))
|
||||
app.use(cors({origin:'*'}))
|
||||
dotenv.config()
|
||||
app.post('/add_review', (req,res) => addReviewFromRequest(req,res));
|
||||
app.post('/add_social_review', (req,res) => addSocialReviewFromRequest(req,res));
|
||||
|
||||
app.get('/borne/get_last_reviews', borneHandler.handleGetLastReviews);
|
||||
app.get('/borne/get_review', borneHandler.handleGetReview);
|
||||
app.get('/borne/get_criteres', borneHandler.handleGetCriteres);
|
||||
app.get('/borne/notes_autres', borneHandler.handleGetNotesAutres);
|
||||
app.get('/borne/get_stats', borneHandler.handleGetStats);
|
||||
|
||||
|
||||
startCronJobs();
|
||||
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log("Server démaré sur le port " + process.env.PORT)
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Telereview",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.1",
|
||||
"cron": "^2.1.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^2.3.3"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Telereview",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.1",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^2.1.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^2.3.3"
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,85 @@
|
||||
import { ReseauxAuteur, ReseauxReview } from './structures.js';
|
||||
import conn from '../database.js';
|
||||
import {getSourceId} from '../utils.js';
|
||||
|
||||
/**Récupérer l'id d'un auteur particulier dans la base de donnée s'il existe
|
||||
* @param {ReseauxAuteur} author L'auteur dont on veut l'id*
|
||||
* @returns Une Promise qui donne l'id de l'utilisateur ou null si il n'existe pas
|
||||
*/
|
||||
const getAuteurId = (author) => {
|
||||
return new Promise(async (resolve,reject) => {
|
||||
const sql = "SELECT id FROM reseaux_sociaux_auteurs WHERE nom_utilisateur = ? AND source_id = ? AND lien = ?;"
|
||||
let sourceId = await getSourceId(author.source);
|
||||
let query = conn.query(sql, [author.nom, sourceId, author.lien], (err, res) => {
|
||||
if(err) {
|
||||
reject(new Error(err.message))
|
||||
}else {
|
||||
if(res.length > 0) {
|
||||
resolve(res[0].id);
|
||||
}else {
|
||||
resolve(null);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ajoute un auteur de commentaire sur les réseaux sociaux a la bdd
|
||||
* @param {ReseauxAuteur} author
|
||||
* @returns Une Promise qui donne l'id de l'utilisateur
|
||||
*/
|
||||
const addAuteur = (author, sourceId) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
const sql = "INSERT INTO reseaux_sociaux_auteurs (nom_utilisateur, source_id, lien) VALUES (?);"
|
||||
conn.query(sql, [[author.nom,sourceId, author.lien]], (err, res) => {
|
||||
if(err) {
|
||||
reject(new Error(err.message))
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un avis dans la BDD
|
||||
* @param {ReseauxReview} review L'avis a ajouter
|
||||
* @param {Number} authorId ID de l'auteur dans la bdd
|
||||
* @param {Number} sourceId ID Du réseau social source dans la bdd
|
||||
* @returns une Promise qui renvoie l'ID de la review
|
||||
*/
|
||||
const addReview = (review, authorId, sourceId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = "INSERT INTO reseaux_sociaux_avis (source_id, note, commentaire, auteur_id, lien_source, date) VALUES (?);"
|
||||
conn.query(sql, [[sourceId, review.note, review.commentaire, authorId, review.lien, review.date]], (err, res) => {
|
||||
if(err) {
|
||||
reject(new Error(err.message))
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite une requête POST pour ajouter un avis récupéré sur les réseaux sociaux
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*/
|
||||
export const addSocialReviewFromRequest = async (req,res) => {
|
||||
try {
|
||||
const author = new ReseauxAuteur(req.body.auteur_nom, req.body.source, req.body.auteur_lien);
|
||||
const review = new ReseauxReview(author, req.body.source, req.body.date, req.body.note, req.body.commentaire, req.body.lien)
|
||||
let sourceId = await getSourceId(review.source);
|
||||
let authorId = await getAuteurId(author);
|
||||
if(authorId == null) {
|
||||
authorId = await addAuteur(author, sourceId);
|
||||
}
|
||||
await addReview(review, authorId, sourceId);
|
||||
res.send("success")
|
||||
}catch(err) {
|
||||
res.status(500).send("Error : " + err.message)
|
||||
}
|
||||
import { ReseauxAuteur, ReseauxReview } from './structures.js';
|
||||
import conn from '../database.js';
|
||||
import {getSourceId} from '../utils.js';
|
||||
|
||||
/**Récupérer l'id d'un auteur particulier dans la base de donnée s'il existe
|
||||
* @param {ReseauxAuteur} author L'auteur dont on veut l'id*
|
||||
* @returns Une Promise qui donne l'id de l'utilisateur ou null si il n'existe pas
|
||||
*/
|
||||
const getAuteurId = (author) => {
|
||||
return new Promise(async (resolve,reject) => {
|
||||
const sql = "SELECT id FROM reseaux_sociaux_auteurs WHERE nom_utilisateur = ? AND source_id = ? AND lien = ?;"
|
||||
let sourceId = await getSourceId(author.source);
|
||||
let query = conn.query(sql, [author.nom, sourceId, author.lien], (err, res) => {
|
||||
if(err) {
|
||||
reject(new Error(err.message))
|
||||
}else {
|
||||
if(res.length > 0) {
|
||||
resolve(res[0].id);
|
||||
}else {
|
||||
resolve(null);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ajoute un auteur de commentaire sur les réseaux sociaux a la bdd
|
||||
* @param {ReseauxAuteur} author
|
||||
* @returns Une Promise qui donne l'id de l'utilisateur
|
||||
*/
|
||||
const addAuteur = (author, sourceId) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
const sql = "INSERT INTO reseaux_sociaux_auteurs (nom_utilisateur, source_id, lien) VALUES (?);"
|
||||
conn.query(sql, [[author.nom,sourceId, author.lien]], (err, res) => {
|
||||
if(err) {
|
||||
reject(new Error(err.message))
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un avis dans la BDD
|
||||
* @param {ReseauxReview} review L'avis a ajouter
|
||||
* @param {Number} authorId ID de l'auteur dans la bdd
|
||||
* @param {Number} sourceId ID Du réseau social source dans la bdd
|
||||
* @returns une Promise qui renvoie l'ID de la review
|
||||
*/
|
||||
const addReview = (review, authorId, sourceId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = "INSERT INTO reseaux_sociaux_avis (source_id, note, commentaire, auteur_id, lien_source, date) VALUES (?);"
|
||||
conn.query(sql, [[sourceId, review.note, review.commentaire, authorId, review.lien, review.date]], (err, res) => {
|
||||
if(err) {
|
||||
reject(new Error(err.message))
|
||||
}else {
|
||||
resolve(res.insertId);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite une requête POST pour ajouter un avis récupéré sur les réseaux sociaux
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*/
|
||||
export const addSocialReviewFromRequest = async (req,res) => {
|
||||
try {
|
||||
const author = new ReseauxAuteur(req.body.auteur_nom, req.body.source, req.body.auteur_lien);
|
||||
const review = new ReseauxReview(author, req.body.source, req.body.date, req.body.note, req.body.commentaire, req.body.lien)
|
||||
let sourceId = await getSourceId(review.source);
|
||||
let authorId = await getAuteurId(author);
|
||||
if(authorId == null) {
|
||||
authorId = await addAuteur(author, sourceId);
|
||||
}
|
||||
await addReview(review, authorId, sourceId);
|
||||
res.send("success")
|
||||
}catch(err) {
|
||||
res.status(500).send("Error : " + err.message)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,39 @@
|
||||
import { Review } from "../borne/structures.js";
|
||||
|
||||
export class ReseauxReview extends Review{
|
||||
/**
|
||||
*
|
||||
* @param {ReseauxAuteur} auteur Auteur de l'avis
|
||||
* @param {String} source La source de l'avis
|
||||
* @param {String} date La date de l'avis au format YYYY-MM-DD
|
||||
* @param {Number} note Nombre entre 0 et 10, la note attribuée
|
||||
* @param {String} commentaire Le commentaire laissé par l'utilisateur
|
||||
* @param {String} lien Lien vers le commentaire
|
||||
*/
|
||||
constructor(auteur, source, date, note=null, commentaire=null, lien=null,) {
|
||||
super(auteur,note,source,commentaire);
|
||||
this.lien = lien;
|
||||
this.date = date;
|
||||
if((typeof lien !== "string" && lien != null)) {
|
||||
throw new Error("Lien invalide");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ReseauxAuteur {
|
||||
/**
|
||||
*
|
||||
* @param {String} nom Nom de l'utilisateur
|
||||
* @param {String} source Réseau social de provenance de l'utilisateur
|
||||
* @param {String} lien Lien vers le profil de l'utilisateur
|
||||
*/
|
||||
constructor(nom, source, lien=null) {
|
||||
this.nom = nom;
|
||||
this.source = source;
|
||||
this.lien = lien;
|
||||
|
||||
if((typeof this.nom !== "string") || (typeof this.source !== "string")){
|
||||
throw new Error("Auteur invalide");
|
||||
}
|
||||
}
|
||||
import { Review } from "../borne/structures.js";
|
||||
|
||||
export class ReseauxReview extends Review{
|
||||
/**
|
||||
*
|
||||
* @param {ReseauxAuteur} auteur Auteur de l'avis
|
||||
* @param {String} source La source de l'avis
|
||||
* @param {String} date La date de l'avis au format YYYY-MM-DD
|
||||
* @param {Number} note Nombre entre 0 et 10, la note attribuée
|
||||
* @param {String} commentaire Le commentaire laissé par l'utilisateur
|
||||
* @param {String} lien Lien vers le commentaire
|
||||
*/
|
||||
constructor(auteur, source, date, note=null, commentaire=null, lien=null,) {
|
||||
super(auteur,note,source,commentaire);
|
||||
this.lien = lien;
|
||||
this.date = date;
|
||||
if((typeof lien !== "string" && lien != null)) {
|
||||
throw new Error("Lien invalide");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ReseauxAuteur {
|
||||
/**
|
||||
*
|
||||
* @param {String} nom Nom de l'utilisateur
|
||||
* @param {String} source Réseau social de provenance de l'utilisateur
|
||||
* @param {String} lien Lien vers le profil de l'utilisateur
|
||||
*/
|
||||
constructor(nom, source, lien=null) {
|
||||
this.nom = nom;
|
||||
this.source = source;
|
||||
this.lien = lien;
|
||||
|
||||
if((typeof this.nom !== "string") || (typeof this.source !== "string")){
|
||||
throw new Error("Auteur invalide");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,50 @@
|
||||
/*
|
||||
Dans cette requête il faut replacer DAY_COUNT_DELAY par le nombre de jours sur lequel calculer les stats, STATS_GENERAL_TABLE_NAME par la table dans laquelle mettre les stats globales (par exemple stats_general_jour si les valeurs sont calculées sur 1 jour) et pareil pour STATS_AUTRES_TABLE_NAME
|
||||
C'est un peu bizzare comme technique mais j'ai pas trouvé de meilleur solution
|
||||
*/
|
||||
SET @date_limite = DATE_ADD(NOW(), INTERVAL -DAY_COUNT_DELAY DAY);
|
||||
|
||||
/*
|
||||
On récupère les notes notes moyennes sur la periode, en séparant global, borne et site
|
||||
*/
|
||||
|
||||
SELECT @moyenne_globale:=AVG(note_principale)
|
||||
FROM borne_avis
|
||||
WHERE borne_avis.date > @date_limite;
|
||||
|
||||
SELECT @moyenne_borne:=AVG(note_principale)
|
||||
FROM borne_avis
|
||||
JOIN sources ON sources.id = borne_avis.source_id
|
||||
WHERE borne_avis.date > @date_limite AND sources.nom = "borne";
|
||||
|
||||
SELECT @moyenne_site:=AVG(note_principale)
|
||||
FROM borne_avis
|
||||
JOIN sources ON sources.id = borne_avis.source_id
|
||||
WHERE borne_avis.date > @date_limite AND sources.nom = "website";
|
||||
|
||||
/*
|
||||
On récupère la distribution de sexes
|
||||
*/
|
||||
|
||||
SELECT @stats_f:=COUNT(*) FROM borne_avis
|
||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||
WHERE sexe='f' AND date > @date_limite;
|
||||
SELECT @stats_h:=COUNT(*) FROM borne_avis
|
||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||
WHERE sexe='h' AND date > @date_limite;
|
||||
SELECT @stats_a:=COUNT(*) FROM borne_avis
|
||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||
WHERE sexe='a' AND date > @date_limite;
|
||||
|
||||
SET @dist_sexe = CONCAT(@stats_f,",",@stats_h,",",@stats_a);
|
||||
|
||||
INSERT INTO STATS_GENERAL_TABLE_NAME (moyenne_globale, moyenne_borne, moyenne_site, dist_sexe) VALUES (@moyenne_globale, @moyenne_borne, @moyenne_site, @dist_sexe);
|
||||
|
||||
INSERT INTO STATS_AUTRES_TABLE_NAME (critere_id, note)
|
||||
SELECT critere_id, AVG(note) as moyenne FROM borne_notes_autre
|
||||
WHERE borne_notes_autre.date > @date_limite
|
||||
GROUP BY critere_id
|
||||
|
||||
/*
|
||||
TODO : Calcul de la distribution d'age
|
||||
/*
|
||||
Dans cette requête il faut replacer DAY_COUNT_DELAY par le nombre de jours sur lequel calculer les stats, STATS_GENERAL_TABLE_NAME par la table dans laquelle mettre les stats globales (par exemple stats_general_jour si les valeurs sont calculées sur 1 jour) et pareil pour STATS_AUTRES_TABLE_NAME
|
||||
C'est un peu bizzare comme technique mais j'ai pas trouvé de meilleur solution
|
||||
*/
|
||||
SET @date_limite = DATE_ADD(NOW(), INTERVAL -DAY_COUNT_DELAY DAY);
|
||||
|
||||
/*
|
||||
On récupère les notes notes moyennes sur la periode, en séparant global, borne et site
|
||||
*/
|
||||
|
||||
SELECT @moyenne_globale:=AVG(note_principale)
|
||||
FROM borne_avis
|
||||
WHERE borne_avis.date > @date_limite;
|
||||
|
||||
SELECT @moyenne_borne:=AVG(note_principale)
|
||||
FROM borne_avis
|
||||
JOIN sources ON sources.id = borne_avis.source_id
|
||||
WHERE borne_avis.date > @date_limite AND sources.nom = "borne";
|
||||
|
||||
SELECT @moyenne_site:=AVG(note_principale)
|
||||
FROM borne_avis
|
||||
JOIN sources ON sources.id = borne_avis.source_id
|
||||
WHERE borne_avis.date > @date_limite AND sources.nom = "website";
|
||||
|
||||
/*
|
||||
On récupère la distribution de sexes
|
||||
*/
|
||||
|
||||
SELECT @stats_f:=COUNT(*) FROM borne_avis
|
||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||
WHERE sexe='f' AND date > @date_limite;
|
||||
SELECT @stats_h:=COUNT(*) FROM borne_avis
|
||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||
WHERE sexe='h' AND date > @date_limite;
|
||||
SELECT @stats_a:=COUNT(*) FROM borne_avis
|
||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||
WHERE sexe='a' AND date > @date_limite;
|
||||
|
||||
SET @dist_sexe = CONCAT(@stats_f,",",@stats_h,",",@stats_a);
|
||||
|
||||
INSERT INTO STATS_GENERAL_TABLE_NAME (moyenne_globale, moyenne_borne, moyenne_site, dist_sexe) VALUES (@moyenne_globale, @moyenne_borne, @moyenne_site, @dist_sexe);
|
||||
|
||||
INSERT INTO STATS_AUTRES_TABLE_NAME (critere_id, note)
|
||||
SELECT critere_id, AVG(note) as moyenne FROM borne_notes_autre
|
||||
WHERE borne_notes_autre.date > @date_limite
|
||||
GROUP BY critere_id
|
||||
|
||||
/*
|
||||
TODO : Calcul de la distribution d'age
|
||||
*/
|
||||
@@ -1,69 +1,69 @@
|
||||
import conn from '../database.js';
|
||||
import fs from "fs";
|
||||
import { CronJob } from 'cron';
|
||||
|
||||
/**
|
||||
* Calcules les stats sur une periode donnée et les stocke dans la BDD
|
||||
* @param {Number} timePeriod Periode de temps sur laquelle calculer les données. Par exemple 7 si on veut faire les stats des 7 derniers jours
|
||||
* @param {String} generalTableName Nom de la table dans laquelle mettre les statistiques générales (tables valides : stats_general_jour stats_general_semaine stats_general_mois stats_general_annee)
|
||||
* @param {*} otherTableName Nom de la table dans laquelle mettre les statistiques spécifiques (tables valides : stats_autres_jour stats_autres_autres_autres_mois stats_general_annee)
|
||||
* @returns Une Promise qui résout si la requête a fonctionnée
|
||||
*/
|
||||
const computeStats = async (timePeriod, generalTableName, otherTableName) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = fs.readFileSync("stats/update_request.sql").toString();
|
||||
sql = sql
|
||||
.replace(new RegExp("DAY_COUNT_DELAY",'g'), timePeriod.toString())
|
||||
.replace(new RegExp("STATS_GENERAL_TABLE_NAME", 'g'), generalTableName)
|
||||
.replace(new RegExp('STATS_AUTRES_TABLE_NAME', 'g'), otherTableName)
|
||||
conn.query(sql, (err, res) => {
|
||||
if(err) {
|
||||
reject(err)
|
||||
}else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
export const startCronJobs = () => {
|
||||
//Update les stats journalières tout les jours a minuit
|
||||
new CronJob(
|
||||
'0 0 * * * * ',
|
||||
() => {
|
||||
computeStats(1,"stats_general_jour","stats_autres_jour");
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
//update les stats de la semaine tous les lundis a minuit
|
||||
new CronJob(
|
||||
'0 0 * * 1 * ',
|
||||
() => {
|
||||
computeStats(7,"stats_general_semaine","stats_autres_jour");
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
//update les stats mensuelles les 1er du mois a minuit
|
||||
new CronJob(
|
||||
'0 0 1 * * * ',
|
||||
() => {
|
||||
computeStats(30, "stats_general_mois","stats_autres_mois");
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
//update les stats annuelles les premire de l'an a minuit
|
||||
new CronJob(
|
||||
'0 0 1 1 * * ',
|
||||
() => {
|
||||
computeStats(365, "stats_general_annee","stats_autres_annee");
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
console.log("All cronjobs initiated")
|
||||
import conn from '../database.js';
|
||||
import fs from "fs";
|
||||
import { CronJob } from 'cron';
|
||||
|
||||
/**
|
||||
* Calcules les stats sur une periode donnée et les stocke dans la BDD
|
||||
* @param {Number} timePeriod Periode de temps sur laquelle calculer les données. Par exemple 7 si on veut faire les stats des 7 derniers jours
|
||||
* @param {String} generalTableName Nom de la table dans laquelle mettre les statistiques générales (tables valides : stats_general_jour stats_general_semaine stats_general_mois stats_general_annee)
|
||||
* @param {*} otherTableName Nom de la table dans laquelle mettre les statistiques spécifiques (tables valides : stats_autres_jour stats_autres_autres_autres_mois stats_general_annee)
|
||||
* @returns Une Promise qui résout si la requête a fonctionnée
|
||||
*/
|
||||
const computeStats = async (timePeriod, generalTableName, otherTableName) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sql = fs.readFileSync("stats/update_request.sql").toString();
|
||||
sql = sql
|
||||
.replace(new RegExp("DAY_COUNT_DELAY",'g'), timePeriod.toString())
|
||||
.replace(new RegExp("STATS_GENERAL_TABLE_NAME", 'g'), generalTableName)
|
||||
.replace(new RegExp('STATS_AUTRES_TABLE_NAME', 'g'), otherTableName)
|
||||
conn.query(sql, (err, res) => {
|
||||
if(err) {
|
||||
reject(err)
|
||||
}else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
export const startCronJobs = () => {
|
||||
//Update les stats journalières tout les jours a minuit
|
||||
new CronJob(
|
||||
'0 0 * * * * ',
|
||||
() => {
|
||||
computeStats(1,"stats_general_jour","stats_autres_jour");
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
//update les stats de la semaine tous les lundis a minuit
|
||||
new CronJob(
|
||||
'0 0 * * 1 * ',
|
||||
() => {
|
||||
computeStats(7,"stats_general_semaine","stats_autres_jour");
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
//update les stats mensuelles les 1er du mois a minuit
|
||||
new CronJob(
|
||||
'0 0 1 * * * ',
|
||||
() => {
|
||||
computeStats(30, "stats_general_mois","stats_autres_mois");
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
//update les stats annuelles les premire de l'an a minuit
|
||||
new CronJob(
|
||||
'0 0 1 1 * * ',
|
||||
() => {
|
||||
computeStats(365, "stats_general_annee","stats_autres_annee");
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
console.log("All cronjobs initiated")
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
import conn from './database.js';
|
||||
|
||||
/**
|
||||
* Renvoie l'ID dans la BDD d'une source de donnée
|
||||
* @param {String} source la source dont on veut récup l'id
|
||||
* @returns une promise qui renvoie l'id de la source
|
||||
*/
|
||||
export const getSourceId = (source) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = "SELECT id from sources WHERE nom = ?";
|
||||
conn.query(sql, [source], (err, res) => {
|
||||
if(res.length == 0) {
|
||||
reject(new Error("Invalid source"))
|
||||
}else {
|
||||
resolve(res[0].id);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
import conn from './database.js';
|
||||
|
||||
/**
|
||||
* Renvoie l'ID dans la BDD d'une source de donnée
|
||||
* @param {String} source la source dont on veut récup l'id
|
||||
* @returns une promise qui renvoie l'id de la source
|
||||
*/
|
||||
export const getSourceId = (source) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = "SELECT id from sources WHERE nom = ?";
|
||||
conn.query(sql, [source], (err, res) => {
|
||||
if(res.length == 0) {
|
||||
reject(new Error("Invalid source"))
|
||||
}else {
|
||||
resolve(res[0].id);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
2
code/setup.sh
Executable file
2
code/setup.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
sudo modprobe v4l2loopback devices=2
|
||||
3
code/video_loopback/Dockerfile
Normal file
3
code/video_loopback/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM alpine:latest
|
||||
RUN apk add --no-cache ffmpeg
|
||||
CMD ["ffmpeg","-video_size","640x480","-f","video4linux2","-i","/dev/video0","-codec","copy","-f","v4l2","/dev/video1","-codec","copy","-f","v4l2","/dev/video2", "-loglevel","warning"]
|
||||
Reference in New Issue
Block a user