Skip to content
Snippets Groups Projects
Commit 162fdeac authored by Aymeric Chaumont's avatar Aymeric Chaumont
Browse files

Merge branch 'collaborative' into 'main'

Collaborative

See merge request !41
parents c2658bc9 77e1bc4a
No related branches found
No related tags found
1 merge request!41Collaborative
Pipeline #44347 passed
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Module to interact with the database Module to interact with the database
""" """
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
from fastapi import HTTPException
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.sql import func from sqlalchemy.sql import func
from sqlalchemy import Time, Date, cast from sqlalchemy import Time, Date, cast
...@@ -44,7 +45,8 @@ def add_slot(slots_list, start_time, end_time, function): ...@@ -44,7 +45,8 @@ def add_slot(slots_list, start_time, end_time, function):
average_waiting_time = function(start_time, end_time) average_waiting_time = function(start_time, end_time)
if average_waiting_time: if average_waiting_time:
name = 60 * start_time.hour + start_time.minute name = 60 * start_time.hour + start_time.minute
slots_list.append(schemas.RecordRead(name=name, time=average_waiting_time)) slots_list.append(schemas.RecordRead(
name=name, time=average_waiting_time))
def get_avg_graph_points(place: str, weekday: int, min_time: time, def get_avg_graph_points(place: str, weekday: int, min_time: time,
...@@ -149,18 +151,8 @@ def get_comments(place: str, page: int, db: Session): ...@@ -149,18 +151,8 @@ def get_comments(place: str, page: int, db: Session):
models.Comments.published_at.desc(), models.Comments.published_at.desc(),
models.Comments.id.desc()).all() models.Comments.id.desc()).all()
else: else:
comments = db.query( comments = db.query(models.Comments, models.Users.username).join(models.Users).filter(models.Comments.place == place).order_by(
models.Comments, models.Comments.published_at.desc(), models.Comments.id.desc()).slice((page - 1) * 20, page * 20).all()
models.Users.username).join(
models.Users).filter(
models.Comments.place == place).order_by(
models.Comments.published_at.desc(),
models.Comments.id.desc()).slice(
(page -
1) *
20,
page *
20).all()
comments_list = list(schemas.Comment( comments_list = list(schemas.Comment(
**comment.__dict__, username=username) for comment, username in comments) **comment.__dict__, username=username) for comment, username in comments)
comments_list.reverse() comments_list.reverse()
...@@ -290,9 +282,18 @@ def init_user(db: Session): ...@@ -290,9 +282,18 @@ def init_user(db: Session):
def get_user(cookie: str, db: Session): def get_user(cookie: str, db: Session):
""" Get user infos """ """ Get user infos """
user = db.query(models.Users).filter(models.Users.cookie == cookie).one() try:
user = db.query(models.Users).filter(
models.Users.cookie == cookie).one()
except BaseException:
raise HTTPException(status_code=401, detail="Invalid cookie")
if pytz.timezone("Europe/Paris").localize(user.expiration_date) < datetime.now(tz=pytz.timezone("Europe/Paris")): if pytz.timezone("Europe/Paris").localize(user.expiration_date) < datetime.now(tz=pytz.timezone("Europe/Paris")):
return user.cookie = None
db.add(user)
db.commit()
raise HTTPException(status_code=401, detail="Expired cookie")
return user return user
...@@ -329,6 +330,111 @@ def update_user(user: schemas.User, user_info: dict, db: Session): ...@@ -329,6 +330,111 @@ def update_user(user: schemas.User, user_info: dict, db: Session):
def end_session(cookie: str, db: Session): def end_session(cookie: str, db: Session):
user = db.query(models.Users).filter(models.Users.cookie == cookie).one() user = db.query(models.Users).filter(models.Users.cookie == cookie).one()
user.expiration_date = datetime.now(tz=pytz.timezone("Europe/Paris")) user.expiration_date = datetime.now(tz=pytz.timezone("Europe/Paris"))
user.cookie = None
db.add(user) db.add(user)
db.commit() db.commit()
return return
def delete_user(cookie: str, db: Session):
db.query(models.Users).filter(models.Users.cookie == cookie).delete()
db.commit()
return
# Define CRUD operations for data collection
def get_records(place: str, db: Session):
records = db.query(models.Records).filter(
models.Records.place == place).order_by(models.Records.date.desc()).all()
return records
def create_record(record: schemas.RecordBase, db: Session):
db_record = models.Records(**record.dict())
db.add(db_record)
db.commit()
db.refresh(db_record)
return db_record
def delete_record(id: int, db: Session):
if id == 0:
db.query(models.Records).delete()
else:
db.query(models.Records).filter(models.Records.id == id).delete()
db.commit()
return
def get_collaborative_records(place: str, db: Session):
records = db.query(models.CollaborativeRecords).filter(
models.CollaborativeRecords.place == place).order_by(models.CollaborativeRecords.date.desc()).all()
return [schemas.CollaborativeRecords(**record.__dict__) for record in records]
def create_collaborative_record(user: schemas.User, place: str, db: Session):
current_date = datetime.now(tz=pytz.timezone("Europe/Paris"))
date, weekday, current_time = current_date.date(
), current_date.weekday(), current_date.time()
try:
time_slot = db.query(
models.OpeningHours).filter(
models.OpeningHours.place == place,
models.OpeningHours.day == weekday,
models.OpeningHours.open_time <= current_time,
models.OpeningHours.close_time >= current_time).one()
except BaseException:
raise HTTPException(status_code=404, detail="No restaurant opened")
last_record = db.query(models.CollaborativeRecords).filter(
models.CollaborativeRecords.user_id == user.id).order_by(models.CollaborativeRecords.date.desc()).first()
if not last_record or last_record.date <= datetime.combine(date, time_slot.open_time):
db_record = models.CollaborativeRecords(
user_id=user.id, place=place, date=current_date)
db.add(db_record)
db.commit()
db.refresh(db_record)
return db_record
raise HTTPException(status_code=406, detail="Client already registered")
def update_collaborative_record(user: schemas.User, db: Session):
current_date = datetime.now(tz=pytz.timezone("Europe/Paris"))
date, weekday, current_time = current_date.date(
), current_date.weekday(), current_date.time()
last_record = db.query(models.CollaborativeRecords).filter(
models.CollaborativeRecords.user_id == user.id).order_by(models.CollaborativeRecords.date.desc()).first()
try:
time_slot = db.query(
models.OpeningHours).filter(
models.OpeningHours.place == last_record.place,
models.OpeningHours.day == weekday,
models.OpeningHours.open_time <= current_time,
models.OpeningHours.close_time >= current_time).one()
except BaseException:
raise HTTPException(status_code=404, detail="No restaurant opened")
if last_record.date >= datetime.combine(date, time_slot.open_time) and not last_record.waiting_time:
last_record.waiting_time = current_date - \
pytz.timezone("Europe/Paris").localize(last_record.date)
print(last_record.waiting_time)
db.add(last_record)
db.commit()
db.refresh(last_record)
return schemas.CollaborativeRecords(**last_record.__dict__)
raise HTTPException(status_code=406, detail="Client already registered")
def delete_collaborative_record(id: int, db: Session):
if id == 0:
db.query(models.CollaborativeRecords).delete()
else:
db.query(models.CollaborativeRecords).filter(
models.CollaborativeRecords.id == id).delete()
db.commit()
return
""" """
Models of the database for magasin app Models of the database for magasin app
""" """
from sqlalchemy import Column, ForeignKey, Integer, DateTime, Float, Interval, String, Text, Boolean, Time from sqlalchemy import Column, ForeignKey, Integer, DateTime, Float, Interval, String, Text, Time
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from db.database import Base from db.database import Base
class CollaborativeRecords(Base):
"""CollaborativeRecords sql table model"""
__tablename__ = "collection"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"))
place = Column(String(30))
date = Column(DateTime)
waiting_time = Column(Interval)
class Records(Base): class Records(Base):
"""Records sql table model""" """Records sql table model"""
__tablename__ = "records" __tablename__ = "records"
...@@ -62,3 +73,4 @@ class Users(Base): ...@@ -62,3 +73,4 @@ class Users(Base):
cookie = Column(String(50)) cookie = Column(String(50))
expiration_date = Column(DateTime) expiration_date = Column(DateTime)
comments = relationship("Comments") comments = relationship("Comments")
comments = relationship("CollaborativeRecords")
...@@ -31,7 +31,16 @@ class RecordRead(BaseModel): ...@@ -31,7 +31,16 @@ class RecordRead(BaseModel):
time: int time: int
class CollaborativeRecords(BaseModel):
"""CollaborativeRecords schema"""
user_id: int = Field(..., title="Id of the user timed")
place: str = Field(..., title="Name of the RU corresponding the given record")
date: datetime = Field(..., title="Date of the record")
waiting_time: Optional[timedelta] = Field(default=None, title="Caculated waiting time for timed person")
# Comments Data structure # Comments Data structure
class CommentBase(BaseModel): class CommentBase(BaseModel):
"""Comments base schema""" """Comments base schema"""
content: str = Field(..., title="Content of the comment posted") content: str = Field(..., title="Content of the comment posted")
......
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv from dotenv import load_dotenv
import os
from fastapi import Depends
from sqlalchemy.orm import Session
from typing import List
from threading import Thread from threading import Thread
import json import os
from db import database, models, schemas from db import database, models
from db.database import get_db
from routers import * from routers import *
from video_capture import handle_cameras from video_capture import handle_cameras
...@@ -45,29 +40,4 @@ app.include_router(comments.router) ...@@ -45,29 +40,4 @@ app.include_router(comments.router)
app.include_router(news.router) app.include_router(news.router)
app.include_router(authentication.router) app.include_router(authentication.router)
app.include_router(websocket.router) app.include_router(websocket.router)
app.include_router(records.router)
@app.get('/api/records', response_model=List[schemas.Record])
async def get_records(place: str, db: Session = Depends(get_db)):
return db.query(models.Records).filter(models.Records.place ==
place).order_by(models.Records.date.desc()).all()
@app.post('/api/records', response_model=schemas.Record)
async def post_records(record: schemas.RecordBase, db: Session = Depends(get_db)):
db_record = models.Records(**record.dict())
db.add(db_record)
db.commit()
db.refresh(db_record)
await websocket.manager.broadcast(json.dumps({"type": "data"}))
return db_record
@app.delete('/api/records', response_model=None)
async def del_records(id: int, db: Session = Depends(get_db)):
if id == 0:
db.query(models.Records).delete()
else:
db.query(models.Records).filter(models.Records.id == id).delete()
db.commit()
return
...@@ -3,7 +3,7 @@ asgiref==3.5.2 ...@@ -3,7 +3,7 @@ asgiref==3.5.2
click==8.1.3 click==8.1.3
fastapi==0.78.0 fastapi==0.78.0
h11==0.13.0 h11==0.13.0
idna==3.3 idna==2.8
keras==2.9.0 keras==2.9.0
numpy==1.23.0 numpy==1.23.0
opencv-python==4.6.0.66 opencv-python==4.6.0.66
...@@ -16,3 +16,4 @@ SQLAlchemy==1.4.19 ...@@ -16,3 +16,4 @@ SQLAlchemy==1.4.19
python-dotenv==0.18.0 python-dotenv==0.18.0
PyMySQL==1.0.2 PyMySQL==1.0.2
pytz==2022.1 pytz==2022.1
requests==2.25.1
\ No newline at end of file
...@@ -3,3 +3,4 @@ from . import comments ...@@ -3,3 +3,4 @@ from . import comments
from . import news from . import news
from . import stats from . import stats
from . import websocket from . import websocket
from . import records
from fastapi import APIRouter, HTTPException, Response, Cookie, Depends
from sqlalchemy.orm import Session
from typing import List
from db import schemas, crud
from db.database import get_db
router = APIRouter(prefix="/api")
@router.get('/records', response_model=List[schemas.Record], tags=["records"])
async def get_records(place: str, db: Session = Depends(get_db)):
return crud.get_records(place, db)
@router.post('/records', response_model=schemas.Record, tags=["records"])
async def stats(record: schemas.RecordBase, db: Session = Depends(get_db)):
return crud.create_record(record, db)
@router.delete('/records', response_model=None, tags=["records"])
async def stats(id: int, db: Session = Depends(get_db)):
return crud.delete_record(id, db)
@router.get('/collection', response_model=List[schemas.CollaborativeRecords], tags=["data collection"])
async def get_collection_records(place: str, db: Session = Depends(get_db)):
return crud.get_collaborative_records(place, db)
@router.post("/collection/start/{place}", response_model=schemas.CollaborativeRecords, tags=["data collection"])
async def create_new_record(response: Response, place: str, connect_id: str = Cookie(default=None), db: Session = Depends(get_db)):
if connect_id:
try:
user = crud.get_user(connect_id, db)
except HTTPException:
response.delete_cookie("connect_id")
user = crud.init_user(db)
else:
user = crud.init_user(db)
db_record = crud.create_collaborative_record(user, place, db)
response.set_cookie(key="connect_id", value=user.cookie)
return schemas.CollaborativeRecords(**db_record.__dict__)
@router.post("/collection/stop", response_model=schemas.CollaborativeRecords, tags=["data collection"])
async def end_new_record(response: Response, connect_id: str = Cookie(...), db: Session = Depends(get_db)):
user = crud.get_user(connect_id, db)
db_record = crud.update_collaborative_record(user, db)
if not user.username:
crud.delete_user(user.cookie, db)
response.delete_cookie(key="connect_id")
return db_record
@router.delete('/collection', response_model=None, tags=["data collection"])
async def delete_record(id: int, db: Session = Depends(get_db)):
return crud.delete_collaborative_record(id, db)
...@@ -81,18 +81,6 @@ export default function Graph({ place, lastMessage }) { ...@@ -81,18 +81,6 @@ export default function Graph({ place, lastMessage }) {
bottom: 5, bottom: 5,
}} }}
> >
{checked ? (
<Line
data={avgData}
type="monotone"
dataKey="time"
stroke="#FF0000"
strokeWidth={2}
dot={false}
/>
) : (
<div />
)}
<defs> <defs>
<linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="10%" stopColor="#ff0000" stopOpacity={0.55} /> <stop offset="10%" stopColor="#ff0000" stopOpacity={0.55} />
...@@ -136,6 +124,18 @@ export default function Graph({ place, lastMessage }) { ...@@ -136,6 +124,18 @@ export default function Graph({ place, lastMessage }) {
fillOpacity={1} fillOpacity={1}
fill="url(#colorGradient)" fill="url(#colorGradient)"
/> />
{checked ? (
<Line
data={avgData}
type="monotone"
dataKey="time"
stroke="#FF0000"
strokeWidth={2}
dot={false}
/>
) : (
<div />
)}
</ComposedChart> </ComposedChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
......
import React from "react";
import Table from "react-bootstrap/Table";
import "../styles/Timetable.css";
export default function Timetable(schedule) {
const timetable = schedule.schedule;
return (
<div className="timetable">
<Table className="table table-striped table-bordered">
<tbody>
<tr>
<th>Lundi</th>
<th>{timetable["LundiMidi"] != null ? timetable["LundiMidi"] : "-"}</th>
<th>{timetable["LundiSoir"] != null ? timetable["LundiSoir"] : "-"}</th>
</tr>
<tr>
<th>Mardi</th>
<th>{timetable["MardiMidi"] != null ? timetable["MardiMidi"] : "-"}</th>
<th>{timetable["MardiSoir"] != null ? timetable["MardiSoir"] : "-"}</th>
</tr>
<tr>
<th>Mercredi</th>
<th>{timetable["MercrediMidi"] != null ? timetable["MercrediMidi"] : "-"}</th>
<th>{timetable["MercrediSoir"] != null ? timetable["MercrediSoir"] : "-"}</th>
</tr>
<tr>
<th>Jeudi</th>
<th>{timetable["JeudiMidi"] != null ? timetable["JeudiMidi"] : "-"}</th>
<th>{timetable["JeudiSoir"] != null ? timetable["JeudiSoir"] : "-"}</th>
</tr>
<tr>
<th>Vendredi</th>
<th>{timetable["VendrediMidi"] != null ? timetable["VendrediMidi"] : "-"}</th>
<th>{timetable["VendrediSoir"] != null ? timetable["VendrediSoir"] : "-"}</th>
</tr>
</tbody>
</Table>
</div>
);
}
export { default as Header } from "./Header"; export { default as Header } from "./Header";
export { default as Footer } from "./Footer"; export { default as Footer } from "./Footer";
export { default as Timetable } from "./Timetable";
export { default as WaitingTime } from "./WaitingTime"; export { default as WaitingTime } from "./WaitingTime";
export { default as Graph } from "./Graph"; export { default as Graph } from "./Graph";
export { default as Comments } from "./Comments"; export { default as Comments } from "./Comments";
.timetable{
display: inline-block;
border-radius: 5px;
text-align: center;
}
\ No newline at end of file
...@@ -8,9 +8,50 @@ ...@@ -8,9 +8,50 @@
width: 50%; width: 50%;
flex-direction: column; flex-direction: column;
align-content: center; align-content: center;
align-items: center;
overflow: hidden; overflow: hidden;
} }
#restaurant-start-button {
width: fit-content;
border: none;
border-radius: 5px;
padding: 0.1rem;
padding-left: 0.3rem;
padding-right: 0.3rem;
margin-top: 2rem;
}
.restaurant-button-disabled {
background-color: rgb(66, 75, 83);
color: #b9b9b9;
}
.restaurant-button-active {
background-color: rgb(33, 37, 41);
color: white;
}
.restaurant-button-active:hover {
box-shadow: 0px 0px 5px white;
}
#restaurant-end-button {
width: fit-content;
background-color: #83000d;
color: white;
border: none;
border-radius: 5px;
padding: 0.1rem;
padding-left: 0.3rem;
padding-right: 0.3rem;
margin-top: 2rem;
}
#restaurant-end-button:hover {
box-shadow: 0px 0px 5px rgb(33, 37, 41);
}
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
#restaurant-main-page { #restaurant-main-page {
width: 100%; width: 100%;
......
import React from "react"; import React /*, { useState } */ from "react";
// import axios from "axios";
import { Graph, WaitingTime, Comments } from "../components"; import { Graph, WaitingTime, Comments } from "../components";
import "../styles/restaurant.css"; import "../styles/restaurant.css";
// const instance = axios.create({
// withCredentials: true,
// baseURL: process.env.REACT_APP_BASE_URL_BACK,
// });
export default function RestaurantPage({ selection, lastMessage }) { export default function RestaurantPage({ selection, lastMessage }) {
// const [started, setStarted] = useState(false);
// const [disabled, setDisabled] = useState(false);
// const Start = () => {
// instance
// .post(`collection/start/${encodeURIComponent(selection.name)}`)
// .then(() => {
// setStarted(true);
// })
// .catch((e) => {
// setDisabled(true);
// alert(
// "Il semblerait que tu aies déjà renseigné un temps d'attente sur ce créneau. Merci de ta participation, n'hésite pas à te chronométrer de nouveau la prochaine fois !",
// );
// console.log(e.response.data);
// });
// };
// const End = () => {
// instance
// .post("collection/stop")
// .then(() => {
// setStarted(false);
// setDisabled(true);
// })
// .catch((e) => {
// setDisabled(true);
// alert(
// "Il semblerait que tu aies déjà renseigné un temps d'attente sur ce créneau. Merci de ta participation, n'hésite pas à te chronométrer de nouveau la prochaine fois !",
// );
// console.log(e);
// });
// };
return ( return (
<> <>
{selection && ( {selection && (
...@@ -13,6 +53,14 @@ export default function RestaurantPage({ selection, lastMessage }) { ...@@ -13,6 +53,14 @@ export default function RestaurantPage({ selection, lastMessage }) {
<div className="restaurant-container" id="restaurant-main-page"> <div className="restaurant-container" id="restaurant-main-page">
<WaitingTime place={selection.name} lastMessage={lastMessage} /> <WaitingTime place={selection.name} lastMessage={lastMessage} />
<Graph place={selection.name} type="current" lastMessage={lastMessage} /> <Graph place={selection.name} type="current" lastMessage={lastMessage} />
{/* <button
id={`restaurant-${started ? "end" : "start"}-button`}
onClick={started ? End : Start}
className={disabled ? "restaurant-button-disabled" : "restaurant-button-active"}
disabled={disabled}
>
{started ? "Fini !!" : "Départ !!"}
</button> */}
</div> </div>
<Comments place={selection.name} lastMessage={lastMessage} /> <Comments place={selection.name} lastMessage={lastMessage} />
{/*<Graph place={selection.name} type="avg" />*/} {/*<Graph place={selection.name} type="avg" />*/}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment