Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
1 result

Target

Select target project
  • raptornythorink/eatfast-website
1 result
Select Git revision
  • main
1 result
Show changes

Commits on Source 9

Showing
with 219 additions and 248 deletions
...@@ -4,6 +4,7 @@ Module to interact with the database ...@@ -4,6 +4,7 @@ Module to interact with the database
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
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 uuid import uuid4 from uuid import uuid4
import secrets import secrets
import pytz import pytz
...@@ -17,87 +18,60 @@ def get_waiting_time(place: str, db: Session): ...@@ -17,87 +18,60 @@ def get_waiting_time(place: str, db: Session):
""" Get the last estimated waiting time for the given place """ """ Get the last estimated waiting time for the given place """
current_date = datetime.now(tz=pytz.timezone("Europe/Paris")) current_date = datetime.now(tz=pytz.timezone("Europe/Paris"))
weekday, current_time = current_date.weekday(), current_date.time() weekday, current_time = current_date.weekday(), current_date.time()
data = {"status": False, "waiting_time": None, "next_timetable": None} opening_hours = db.query(models.OpeningHours.open_time, models.OpeningHours.close_time).filter(
first_timeslot = get_timeslot(place, weekday, True, db) models.OpeningHours.place == place, models.OpeningHours.day == weekday).all()
if first_timeslot and current_time < first_timeslot[0]: for time_slot in opening_hours:
data["next_timetable"] = "{:d}h{:02d}".format( if current_time < time_slot.open_time:
first_timeslot[0].hour, first_timeslot[0].minute) return schemas.WaitingTime(next_timetable=time_slot.open_time.strftime('%Hh%M'))
return data elif current_time <= time_slot.close_time:
elif first_timeslot and current_time <= first_timeslot[1]: limit = datetime.combine(date.today(), time_slot.open_time)
last_record = db.query( last_record = db.query(models.Records.waiting_time).filter(models.Records.place == place).filter(
models.Records.waiting_time models.Records.date >= limit).order_by(models.Records.date.desc()).first()
).filter( waiting_time = None
models.Records.place == place
).order_by(
models.Records.date.desc()
).first()
if last_record:
waiting_time = last_record.waiting_time
waiting_time = round(waiting_time.total_seconds() / 60)
data["status"] = True
data["waiting_time"] = waiting_time
return data
second_timeslot = get_timeslot(place, weekday, False, db)
if second_timeslot and current_time < second_timeslot[0]:
data["next_timetable"] = "{:d}h{:02d}".format(
second_timeslot[0].hour, second_timeslot[0].minute)
return data
elif second_timeslot and current_time <= second_timeslot[1]:
last_record = db.query(
models.Records.waiting_time
).filter(
models.Records.place == place
).order_by(
models.Records.date.desc()
).first()
if last_record: if last_record:
waiting_time = last_record.waiting_time waiting_time = round(
waiting_time = round(waiting_time.total_seconds() / 60) last_record.waiting_time.total_seconds() / 60)
data["status"] = True return schemas.WaitingTime(status=True, waiting_time=waiting_time)
data["waiting_time"] = waiting_time return schemas.WaitingTime()
return data
return data
# Define some utils function
def shift_time(t: time, delta: timedelta):
return (datetime.combine(date(1, 1, 1), t) + delta).time()
def add_slot(slots_list, start_time, end_time, function):
average_waiting_time = function(start_time, end_time)
if average_waiting_time:
name = 60 * start_time.hour + start_time.minute
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,
max_time: time, interval: timedelta, db: Session): max_time: time, interval: timedelta, db: Session):
""" Get the average waiting time for each interval between two time steps """ """ Get the average waiting time for each interval between two time steps """
def shift_time(t: time, delta: timedelta):
return (datetime.combine(date(1, 1, 1), t) + delta).time()
def avg_time_query(start_time, end_time): def avg_time_query(start_time, end_time):
records = db.query( records = db.query(
(func.round( func.round(
func.avg( func.avg(
3600 * func.extract('HOUR', models.Records.waiting_time) + 60 * func.extract('HOUR', models.Records.waiting_time) +
60 * func.extract('MINUTE', models.Records.waiting_time) + func.extract('MINUTE', models.Records.waiting_time))
func.extract('SECOND', models.Records.waiting_time)) )
)) / 60
).filter( ).filter(
models.Records.place == place, models.Records.place == place,
func.weekday(models.Records.date) == weekday, func.weekday(models.Records.date) == weekday,
(func.extract('HOUR', models.Records.date) > start_time.hour) | cast(models.Records.date, Time) >= start_time,
((func.extract('HOUR', models.Records.date) == start_time.hour) & cast(models.Records.date, Time) <= end_time,
(func.extract('MINUTE', models.Records.date) >= start_time.minute)), ).first()
(func.extract('HOUR', models.Records.date) < end_time.hour) | if records[0] or records[0] == 0:
((func.extract('HOUR', models.Records.date) == end_time.hour) &
(func.extract('MINUTE', models.Records.date) < end_time.minute)),
).one()
if records[0]:
return int(records[0]) return int(records[0])
return None return None
def add_slot(slots_list, start_time, end_time):
average_waiting_time = avg_time_query(start_time, end_time)
if average_waiting_time:
name = 60 * start_time.hour + start_time.minute
slots_list.append({'name': name, 'time': average_waiting_time})
stats = [] stats = []
start_time, end_time = min_time, shift_time(min_time, interval) start_time, end_time = min_time, shift_time(min_time, interval)
while start_time < max_time: while start_time < max_time:
add_slot(stats, start_time, end_time) add_slot(stats, start_time, end_time, avg_time_query)
start_time, end_time = end_time, shift_time(end_time, interval) start_time, end_time = end_time, shift_time(end_time, interval)
return stats return stats
...@@ -108,13 +82,12 @@ def get_avg_graph(place: str, db: Session): ...@@ -108,13 +82,12 @@ def get_avg_graph(place: str, db: Session):
for the current or next available timeslot""" for the current or next available timeslot"""
current_date = datetime.now(tz=pytz.timezone("Europe/Paris")) current_date = datetime.now(tz=pytz.timezone("Europe/Paris"))
weekday, current_time = current_date.weekday(), current_date.time() weekday, current_time = current_date.weekday(), current_date.time()
first_timeslot = get_timeslot(place, weekday, True, db) opening_hours = db.query(models.OpeningHours.open_time, models.OpeningHours.close_time).filter(
if first_timeslot and current_time <= first_timeslot[1]: models.OpeningHours.place == place, models.OpeningHours.day == weekday).order_by(models.OpeningHours.open_time).all()
return get_avg_graph_points(
place, weekday, first_timeslot[0], first_timeslot[1], timedelta(minutes=5), db) for time_slot in opening_hours:
second_timeslot = get_timeslot(place, weekday, False, db) if current_time <= time_slot.close_time:
if second_timeslot and current_time <= second_timeslot[1]: return get_avg_graph_points(place, weekday, time_slot.open_time, time_slot.close_time, timedelta(minutes=5), db)
return get_avg_graph_points(place, weekday, second_timeslot[0], second_timeslot[1], timedelta(minutes=5), db)
return [] return []
...@@ -122,43 +95,27 @@ def get_current_graph_points(place: str, current_date: date, ...@@ -122,43 +95,27 @@ def get_current_graph_points(place: str, current_date: date,
min_time: time, max_time: time, interval: timedelta, db: Session): min_time: time, max_time: time, interval: timedelta, db: Session):
""" Get the waiting time for each interval between two time steps for the current timeslot """ """ Get the waiting time for each interval between two time steps for the current timeslot """
def shift_time(t: time, delta: timedelta): def current_time_query(start_time, end_time):
return (datetime.combine(date(1, 1, 1), t) + delta).time()
def avg_time_query(start_time, end_time):
records = db.query( records = db.query(
(func.round( func.round(
func.avg( func.avg(
3600 * func.extract('HOUR', models.Records.waiting_time) + 60 * func.extract('HOUR', models.Records.waiting_time) +
60 * func.extract('MINUTE', models.Records.waiting_time) + func.extract('MINUTE', models.Records.waiting_time))
func.extract('SECOND', models.Records.waiting_time)) )
)) / 60
).filter( ).filter(
models.Records.place == place, models.Records.place == place,
func.extract('YEAR', models.Records.date) == current_date.year, cast(models.Records.date, Date) == current_date,
func.extract('MONTH', models.Records.date) == current_date.month, cast(models.Records.date, Time) >= start_time,
func.extract('DAY', models.Records.date) == current_date.day, cast(models.Records.date, Time) <= end_time
(func.extract('HOUR', models.Records.date) > start_time.hour) | ).first()
((func.extract('HOUR', models.Records.date) == start_time.hour) & if records[0] or records[0] == 0:
(func.extract('MINUTE', models.Records.date) >= start_time.minute)),
(func.extract('HOUR', models.Records.date) < end_time.hour) |
((func.extract('HOUR', models.Records.date) == end_time.hour) &
(func.extract('MINUTE', models.Records.date) < end_time.minute)),
).one()
if records[0]:
return int(records[0]) return int(records[0])
return None return None
def add_slot(slots_list, start_time, end_time):
average_waiting_time = avg_time_query(start_time, end_time)
if average_waiting_time:
name = 60 * start_time.hour + start_time.minute
slots_list.append({'name': name, 'time': average_waiting_time})
stats = [] stats = []
start_time, end_time = min_time, shift_time(min_time, interval) start_time, end_time = min_time, shift_time(min_time, interval)
while start_time < max_time: while start_time < max_time:
add_slot(stats, start_time, end_time) add_slot(stats, start_time, end_time, current_time_query)
start_time, end_time = end_time, shift_time(end_time, interval) start_time, end_time = end_time, shift_time(end_time, interval)
return stats return stats
...@@ -169,23 +126,17 @@ def get_current_graph(place: str, db: Session): ...@@ -169,23 +126,17 @@ def get_current_graph(place: str, db: Session):
current_date = datetime.now(tz=pytz.timezone("Europe/Paris")) current_date = datetime.now(tz=pytz.timezone("Europe/Paris"))
weekday, day, current_time = current_date.weekday( weekday, day, current_time = current_date.weekday(
), current_date.date(), current_date.time() ), current_date.date(), current_date.time()
first_timeslot = get_timeslot(place, weekday, True, db) opening_hours = db.query(models.OpeningHours.open_time, models.OpeningHours.close_time).filter(
if first_timeslot and current_time <= first_timeslot[0]: models.OpeningHours.place == place, models.OpeningHours.day == weekday).all()
return [], None, None
elif first_timeslot and current_time <= first_timeslot[1]: for time_slot in opening_hours:
points = get_current_graph_points(place, day, first_timeslot[0], current_time, timedelta(minutes=5), db) if time_slot.open_time <= current_time <= time_slot.close_time:
start_time = 60 * first_timeslot[0].hour + first_timeslot[0].minute points = get_current_graph_points(
end_time = 60 * first_timeslot[1].hour + first_timeslot[1].minute place, day, time_slot.open_time, current_time, timedelta(minutes=5), db)
return points, start_time, end_time start_time = 60 * time_slot.open_time.hour + time_slot.open_time.minute
second_timeslot = get_timeslot(place, weekday, False, db) end_time = 60 * time_slot.close_time.hour + time_slot.close_time.minute
if second_timeslot and current_time <= second_timeslot[0]: return schemas.Graph(data=points, start=start_time, end=end_time)
return [], None, None return schemas.Graph(data=[])
elif second_timeslot and current_time <= second_timeslot[1]:
points = get_current_graph_points(place, day, second_timeslot[0], current_time, timedelta(minutes=5), db)
start_time = 60 * second_timeslot[0].hour + second_timeslot[0].minute
end_time = 60 * second_timeslot[1].hour + second_timeslot[1].minute
return points, start_time, end_time
return [], None, None
# Define CRUD operation for the comments # Define CRUD operation for the comments
...@@ -210,13 +161,17 @@ def get_comments(place: str, page: int, db: Session): ...@@ -210,13 +161,17 @@ def get_comments(place: str, page: int, db: Session):
20, 20,
page * page *
20).all() 20).all()
return list(schemas.Comment(**comment.__dict__, username=username) for comment, username in comments) comments_list = list(schemas.Comment(
**comment.__dict__, username=username) for comment, username in comments)
comments_list.reverse()
return comments_list
def create_comment(user: schemas.User, place: str, new_comments: schemas.CommentBase, db: Session): def create_comment(user: schemas.User, place: str, new_comments: schemas.CommentBase, db: Session):
""" Add a new comment to the database """ """ Add a new comment to the database """
date = datetime.now(tz=pytz.timezone("Europe/Paris")) date = datetime.now(tz=pytz.timezone("Europe/Paris"))
db_comment = models.Comments(**new_comments.dict(), published_at=date, place=place, user_id=user.id) db_comment = models.Comments(
**new_comments.dict(), published_at=date, place=place, user_id=user.id)
db.add(db_comment) db.add(db_comment)
db.commit() db.commit()
db.refresh(db_comment) db.refresh(db_comment)
...@@ -271,24 +226,11 @@ def get_opening_hours(place: str, db: Session): ...@@ -271,24 +226,11 @@ def get_opening_hours(place: str, db: Session):
).filter( ).filter(
models.OpeningHours.place == place models.OpeningHours.place == place
).order_by( ).order_by(
models.OpeningHours.day, models.OpeningHours.timeslot.desc() models.OpeningHours.day, models.OpeningHours.open_time
).all() ).all()
return opening_hours return opening_hours
def get_timeslot(place: str, day: int, timeslot: bool, db: Session):
""" Get the opening hours for the given place and timeslot"""
opening_hours = db.query(
models.OpeningHours.open_time,
models.OpeningHours.close_time,
).filter(
models.OpeningHours.place == place,
models.OpeningHours.day == day,
models.OpeningHours.timeslot == timeslot
).first()
return opening_hours
def create_opening_hours( def create_opening_hours(
new_opening_hours: schemas.OpeningHoursBase, db: Session): new_opening_hours: schemas.OpeningHoursBase, db: Session):
""" Add opening hours to the database """ """ Add opening hours to the database """
...@@ -313,56 +255,19 @@ def delete_opening_hours(id: int, db: Session): ...@@ -313,56 +255,19 @@ def delete_opening_hours(id: int, db: Session):
# Restaurants information # Restaurants information
def get_restaurants(db: Session): def get_restaurants(db: Session):
current_date = datetime.now(tz=pytz.timezone("Europe/Paris")) weekday = datetime.now(tz=pytz.timezone("Europe/Paris")).weekday()
weekday, current_time = current_date.weekday(), current_date.time() places = db.query(models.OpeningHours.place).distinct()
restaurant_names = [
r.place for r in db.query(
models.OpeningHours.place).distinct()]
restaurants = [] restaurants = []
for name in restaurant_names: for place in places:
restaurant = {} opening_hours = db.query(models.OpeningHours).filter(
restaurant["name"] = name models.OpeningHours.place == place.place, models.OpeningHours.day == weekday).all()
first_timeslot = get_timeslot(name, weekday, True, db) opening_hours_formated = [
second_timeslot = get_timeslot(name, weekday, False, db) f"{row.open_time.strftime('%Hh%M')}-{row.close_time.strftime('%Hh%M')}" for row in opening_hours]
timetable = "/".join(opening_hours_formated)
if (first_timeslot and first_timeslot[0] <= current_time < first_timeslot[1]) or ( infos = get_waiting_time(place.place, db)
second_timeslot and second_timeslot[0] <= current_time < second_timeslot[1]): restaurants.append(schemas.Restaurant(
restaurant["status"] = True **infos.dict(), name=place.place, timetable=timetable))
else:
restaurant["status"] = False
if first_timeslot and second_timeslot:
restaurant["timetable"] = (
f"{first_timeslot[0].hour:{'0'}{2}}h{first_timeslot[0].minute:{'0'}{2}}-"
f"{first_timeslot[1].hour:{'0'}{2}}h{first_timeslot[1].minute:{'0'}{2}} / "
f"{second_timeslot[0].hour:{'0'}{2}}h{second_timeslot[0].minute:{'0'}{2}}-"
f"{second_timeslot[1].hour:{'0'}{2}}h{second_timeslot[1].minute:{'0'}{2}}")
elif first_timeslot:
restaurant["timetable"] = (
f"{first_timeslot[0].hour:{'0'}{2}}h{first_timeslot[0].minute:{'0'}{2}}-"
f"{first_timeslot[1].hour:{'0'}{2}}h{first_timeslot[1].minute:{'0'}{2}}")
else:
restaurant["timeslot"] = "-"
if restaurant["status"]:
last_record = db.query(
models.Records
).filter(
models.Records.place == name
).order_by(
models.Records.date.desc()
).first()
if last_record:
waiting_time = last_record.waiting_time
restaurant["waiting_time"] = round(
waiting_time.total_seconds() / 60)
else:
restaurant["waiting_time"] = None
else:
restaurant["waiting_time"] = None
restaurants.append(restaurant)
return restaurants return restaurants
...@@ -373,8 +278,10 @@ def init_user(db: Session): ...@@ -373,8 +278,10 @@ def init_user(db: Session):
""" Add a news to the database """ """ Add a news to the database """
cookie = uuid4() cookie = uuid4()
state = secrets.token_urlsafe(30) state = secrets.token_urlsafe(30)
expiration_date = datetime.now(tz=pytz.timezone("Europe/Paris")) + timedelta(minutes=10) expiration_date = datetime.now(tz=pytz.timezone(
db_user = models.Users(state=state, cookie=cookie, expiration_date=expiration_date) "Europe/Paris")) + timedelta(minutes=10)
db_user = models.Users(state=state, cookie=cookie,
expiration_date=expiration_date)
db.add(db_user) db.add(db_user)
db.commit() db.commit()
db.refresh(db_user) db.refresh(db_user)
...@@ -398,8 +305,10 @@ def delete_state(user: schemas.User, db: Session): ...@@ -398,8 +305,10 @@ def delete_state(user: schemas.User, db: Session):
def update_user(user: schemas.User, user_info: dict, db: Session): def update_user(user: schemas.User, user_info: dict, db: Session):
full_name = f"{user_info['firstName']} {user_info['lastName']}" full_name = f"{user_info['firstName']} {user_info['lastName']}"
expiration_date = datetime.now(tz=pytz.timezone("Europe/Paris")) + timedelta(days=3) expiration_date = datetime.now(
existing_user = db.query(models.Users).filter(models.Users.username == full_name).first() tz=pytz.timezone("Europe/Paris")) + timedelta(days=3)
existing_user = db.query(models.Users).filter(
models.Users.username == full_name).first()
if existing_user: if existing_user:
existing_user.cookie = user.cookie existing_user.cookie = user.cookie
existing_user.expiration_date = expiration_date existing_user.expiration_date = expiration_date
......
...@@ -48,7 +48,6 @@ class OpeningHours(Base): ...@@ -48,7 +48,6 @@ class OpeningHours(Base):
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
place = Column(String(30)) place = Column(String(30))
day = Column(Integer) day = Column(Integer)
timeslot = Column(Boolean)
open_time = Column(Time) open_time = Column(Time)
close_time = Column(Time) close_time = Column(Time)
......
""" """
Pydantic schemas for the magasin app Pydantic schemas for the magasin app
""" """
from typing import Optional from typing import List, Optional
from datetime import datetime, timedelta, time from datetime import datetime, timedelta, time
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
# Records data structure
class RecordBase(BaseModel): class RecordBase(BaseModel):
"""Records base schema""" """Records base schema"""
place: str = Field(..., place: str = Field(...,
...@@ -24,6 +25,13 @@ class Record(RecordBase): ...@@ -24,6 +25,13 @@ class Record(RecordBase):
orm_mode = True orm_mode = True
class RecordRead(BaseModel):
""" Data structure for record in graph """
name: int
time: int
# 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")
...@@ -40,6 +48,7 @@ class Comment(CommentBase): ...@@ -40,6 +48,7 @@ class Comment(CommentBase):
orm_mode = True orm_mode = True
# News data structure
class NewsBase(BaseModel): class NewsBase(BaseModel):
"""News sql table model""" """News sql table model"""
title: str = Field(..., title="Title of the news") title: str = Field(..., title="Title of the news")
...@@ -57,13 +66,19 @@ class News(NewsBase): ...@@ -57,13 +66,19 @@ class News(NewsBase):
orm_mode = True orm_mode = True
# Stats data structure
class WaitingTime(BaseModel):
"""Waiting time schema for reading"""
status: bool = Field(default=False, title="Status of the restaurant for the current hour")
waiting_time: Optional[timedelta] = Field(default=None, title="Waiting time for the restaurant")
next_timetable: Optional[str] = Field(default=None, title="Next time the restaurant will be open")
class OpeningHoursBase(BaseModel): class OpeningHoursBase(BaseModel):
"""Database opening_hours base schema""" """Database opening_hours base schema"""
place: str = Field(..., place: str = Field(...,
title="Name of the RU corresponding the given record") title="Name of the RU corresponding the given record")
day: int = Field(..., title="Day of the week") day: int = Field(..., title="Day of the week")
timeslot: bool = Field(...,
title="Service slot (True for midday, False for evening)")
open_time: time = Field(..., title="Opening time") open_time: time = Field(..., title="Opening time")
close_time: time = Field(..., title="Closing time") close_time: time = Field(..., title="Closing time")
...@@ -76,6 +91,22 @@ class OpeningHours(OpeningHoursBase): ...@@ -76,6 +91,22 @@ class OpeningHours(OpeningHoursBase):
orm_mode = True orm_mode = True
class Restaurant(BaseModel):
"""Restaurant schema for reading"""
name: str
status: bool
waiting_time: Optional[timedelta] = Field(default=None, title="Waiting time for the restaurant")
timetable: str
class Graph(BaseModel):
""" Data structure for current graph display """
data: List[RecordRead] = Field(title="Last records list for the restaurant")
start: Optional[int] = Field(default=None, title="Opening of the RU")
end: Optional[int] = Field(default=None, title="Closure of the RU")
# User data structure
class User(BaseModel): class User(BaseModel):
"""Database user base schema""" """Database user base schema"""
id: int id: int
......
...@@ -2,14 +2,14 @@ from fastapi import FastAPI ...@@ -2,14 +2,14 @@ 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 import os
from db.database import get_db
from fastapi import Depends from fastapi import Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from db import schemas
from typing import List from typing import List
from threading import Thread from threading import Thread
import json
from db import database, models from db import database, models, schemas
from db.database import get_db
from routers import * from routers import *
from video_capture import handle_cameras from video_capture import handle_cameras
...@@ -44,6 +44,7 @@ app.include_router(stats.router) ...@@ -44,6 +44,7 @@ app.include_router(stats.router)
app.include_router(comments.router) 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.get('/api/records', response_model=List[schemas.Record]) @app.get('/api/records', response_model=List[schemas.Record])
...@@ -58,6 +59,7 @@ async def post_records(record: schemas.RecordBase, db: Session = Depends(get_db) ...@@ -58,6 +59,7 @@ async def post_records(record: schemas.RecordBase, db: Session = Depends(get_db)
db.add(db_record) db.add(db_record)
db.commit() db.commit()
db.refresh(db_record) db.refresh(db_record)
await websocket.manager.broadcast(json.dumps({"type": "data"}))
return db_record return db_record
......
from . import authentication from . import authentication
from . import comments from . import comments
from . import news from . import news
from . import opening_hours
from . import stats from . import stats
from . import websocket
...@@ -74,6 +74,8 @@ async def login(code: Optional[str] = None, state: Optional[str] = None, connect ...@@ -74,6 +74,8 @@ async def login(code: Optional[str] = None, state: Optional[str] = None, connect
@router.get("/logout") @router.get("/logout")
async def delete_session(connect_id: str = Cookie(...), db: Session = Depends(get_db)): async def delete_session(connect_id: str = Cookie(...), db: Session = Depends(get_db)):
response = RedirectResponse(f"{os.getenv('AUTH_ROOT')}/logout?{urlencode({'redirect_logout': os.getenv('WEB_ROOT')})}") response = RedirectResponse(f"{os.getenv('AUTH_ROOT')}/logout?{urlencode({'redirect_logout': os.getenv('WEB_ROOT')})}")
try:
response.delete_cookie(key="connect_id") response.delete_cookie(key="connect_id")
crud.end_session(connect_id, db) crud.end_session(connect_id, db)
finally:
return response return response
from fastapi import APIRouter, Body, Cookie, Depends from fastapi import APIRouter, Body, Cookie, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
import json
from db import schemas, crud from db import schemas, crud
from db.database import get_db from db.database import get_db
from routers.websocket import manager
router = APIRouter(prefix="/api", tags=["comments"]) router = APIRouter(prefix="/api", tags=["comments"])
...@@ -18,7 +20,9 @@ async def get_comments(place: str, page: int = 1, db: Session = Depends(get_db)) ...@@ -18,7 +20,9 @@ async def get_comments(place: str, page: int = 1, db: Session = Depends(get_db))
async def create_comment(place: str, connect_id: str = Cookie(...), comment: schemas.CommentBase = Body(...), db: Session = Depends(get_db)): async def create_comment(place: str, connect_id: str = Cookie(...), comment: schemas.CommentBase = Body(...), db: Session = Depends(get_db)):
user = crud.get_user(connect_id, db) user = crud.get_user(connect_id, db)
if user: if user:
return crud.create_comment(user, place, comment, db) saved_comment = crud.create_comment(user, place, comment, db)
await manager.broadcast(json.dumps({"type": "comment", "comment": saved_comment.dict()}, default=str))
return saved_comment
else: else:
raise Exception raise Exception
......
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
import json
from db import schemas, crud from db import schemas, crud
from db.database import get_db from db.database import get_db
from .websocket import manager
router = APIRouter(prefix="/api", tags=["news"]) router = APIRouter(prefix="/api", tags=["news"])
...@@ -16,7 +18,9 @@ async def get_news(place: str, db: Session = Depends(get_db)): ...@@ -16,7 +18,9 @@ async def get_news(place: str, db: Session = Depends(get_db)):
@router.post('/news', response_model=schemas.News) @router.post('/news', response_model=schemas.News)
async def create_news(news: schemas.NewsBase, db: Session = Depends(get_db)): async def create_news(news: schemas.NewsBase, db: Session = Depends(get_db)):
return crud.create_news(news, db) saved_news = crud.create_news(news, db)
await manager.broadcast(json.dumps({"type": "news", "comment": saved_news.__dict__}, default=str))
return saved_news
@router.delete('/news/{id}', response_model=None) @router.delete('/news/{id}', response_model=None)
......
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Tuple from typing import List
from db import schemas, crud from db import schemas, crud
from db.database import get_db from db.database import get_db
router = APIRouter(prefix="/api", tags=["stats"]) router = APIRouter(prefix="/api")
@router.get('/{place}/waiting_time', response_model=dict) @router.get('/{place}/waiting_time', response_model=schemas.WaitingTime, tags=["stats"])
async def waiting_time(place: str, db: Session = Depends(get_db)): async def waiting_time(place: str, db: Session = Depends(get_db)):
return crud.get_waiting_time(place, db) return crud.get_waiting_time(place, db)
@router.get('/{place}/stats/avg_graph', response_model=list) @router.get('/{place}/stats/avg_graph', response_model=list, tags=["stats"])
async def stats(place: str, db: Session = Depends(get_db)): async def stats(place: str, db: Session = Depends(get_db)):
return crud.get_avg_graph(place, db) return crud.get_avg_graph(place, db)
@router.get('/{place}/stats/current_graph', response_model=Tuple[list, int, int]) @router.get('/{place}/stats/current_graph', response_model=schemas.Graph, tags=["stats"])
async def stats(place: str, db: Session = Depends(get_db)): async def stats(place: str, db: Session = Depends(get_db)):
return crud.get_current_graph(place, db) return crud.get_current_graph(place, db)
@router.get('/{place}/opening_hours', @router.get('/{place}/opening_hours',
response_model=List[schemas.OpeningHours]) response_model=List[schemas.OpeningHours], tags=["timetable"])
async def get_opening_hours(place: str, db: Session = Depends(get_db)): async def get_opening_hours(place: str, db: Session = Depends(get_db)):
return crud.get_opening_hours(place, db) return crud.get_opening_hours(place, db)
@router.get('/{place}/opening_hours/{day}/{timeslot}', @router.post('/opening_hours', response_model=schemas.OpeningHours, tags=["timetable"])
response_model=List[schemas.OpeningHours])
async def get_timeslot(place: str, day: int, timeslot: bool, db: Session = Depends(get_db)):
return crud.get_timeslot(place, day, timeslot, db)
@router.post('/opening_hours', response_model=schemas.OpeningHours)
async def create_opening_hours(opening_hours: schemas.OpeningHoursBase, db: Session = Depends(get_db)): async def create_opening_hours(opening_hours: schemas.OpeningHoursBase, db: Session = Depends(get_db)):
return crud.create_opening_hours(opening_hours, db) return crud.create_opening_hours(opening_hours, db)
@router.delete('/opening_hours/{id}', response_model=None) @router.delete('/opening_hours/{id}', response_model=None, tags=["timetable"])
async def delete_opening_hours(id: int, db: Session = Depends(get_db)): async def delete_opening_hours(id: int, db: Session = Depends(get_db)):
return crud.delete_opening_hours(id, db) return crud.delete_opening_hours(id, db)
@router.get('/restaurants', response_model=List[dict]) @router.get('/restaurants', response_model=List[schemas.Restaurant], tags=["timetable"])
async def get_restaurants(db: Session = Depends(get_db)): async def get_restaurants(db: Session = Depends(get_db)):
return crud.get_restaurants(db) return crud.get_restaurants(db)
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from typing import List
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
try:
await connection.send_text(message)
except WebSocketDisconnect:
manager.disconnect(connection)
manager = ConnectionManager()
router = APIRouter(tags=["websocket"])
@router.websocket("/api/ws")
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
try:
while True:
await websocket.receive_text()
except WebSocketDisconnect:
manager.disconnect(websocket)
...@@ -3,11 +3,13 @@ from datetime import datetime, timedelta ...@@ -3,11 +3,13 @@ from datetime import datetime, timedelta
import numpy as np import numpy as np
import keras import keras
from utils.preprocessing import fix_singular_shape, norm_by_imagenet from utils.preprocessing import fix_singular_shape, norm_by_imagenet
from db import models
from dotenv import load_dotenv from dotenv import load_dotenv
import json
import os import os
from db import models
from db.database import SessionLocal from db.database import SessionLocal
from routers.websocket import manager
def handle_cameras(): def handle_cameras():
...@@ -56,7 +58,6 @@ def handle_cameras(): ...@@ -56,7 +58,6 @@ def handle_cameras():
axis=0) axis=0)
pred_map = np.squeeze(model.predict(input_image)) pred_map = np.squeeze(model.predict(input_image))
count_prediction = np.sum(pred_map) count_prediction = np.sum(pred_map)
print(count_prediction)
waiting_time = timedelta( waiting_time = timedelta(
seconds=camera['b_factor'] + seconds=camera['b_factor'] +
int(count_prediction) * int(count_prediction) *
...@@ -68,7 +69,7 @@ def handle_cameras(): ...@@ -68,7 +69,7 @@ def handle_cameras():
waiting_time=waiting_time) waiting_time=waiting_time)
db.add(db_record) db.add(db_record)
db.commit() db.commit()
db.refresh(db_record) manager.broadcast(json.dumps({"type": "data"}))
camera['count'] += 1 camera['count'] += 1
else: else:
camera["cap"] = cv2.VideoCapture( camera["cap"] = cv2.VideoCapture(
......
REACT_APP_BASE_URL_BACK=http://localhost:3001/api REACT_APP_BASE_URL_BACK=http://localhost:3001/api
REACT_APP_SOCKET_URL=ws://localhost:3001/api
\ No newline at end of file
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-spring": "^9.4.5", "react-spring": "^9.4.5",
"react-use-websocket": "^4.2.0",
"recharts": "^2.1.12" "recharts": "^2.1.12"
}, },
"devDependencies": { "devDependencies": {
...@@ -17521,6 +17522,15 @@ ...@@ -17521,6 +17522,15 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.0"
} }
}, },
"node_modules/react-use-websocket": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.2.0.tgz",
"integrity": "sha512-ZovaTlc/tWX6a590fi3kMWImhyoWj46BWJWvO5oucZJzRnVVhYtes2D9g+5MKXjSdR7Es3456hB89v4/1pcBKg==",
"peerDependencies": {
"react": ">= 18.0.0",
"react-dom": ">= 18.0.0"
}
},
"node_modules/react-zdog": { "node_modules/react-zdog": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/react-zdog/-/react-zdog-1.0.11.tgz", "resolved": "https://registry.npmjs.org/react-zdog/-/react-zdog-1.0.11.tgz",
...@@ -34103,6 +34113,12 @@ ...@@ -34103,6 +34113,12 @@
"prop-types": "^15.6.2" "prop-types": "^15.6.2"
} }
}, },
"react-use-websocket": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.2.0.tgz",
"integrity": "sha512-ZovaTlc/tWX6a590fi3kMWImhyoWj46BWJWvO5oucZJzRnVVhYtes2D9g+5MKXjSdR7Es3456hB89v4/1pcBKg==",
"requires": {}
},
"react-zdog": { "react-zdog": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/react-zdog/-/react-zdog-1.0.11.tgz", "resolved": "https://registry.npmjs.org/react-zdog/-/react-zdog-1.0.11.tgz",
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-spring": "^9.4.5", "react-spring": "^9.4.5",
"react-use-websocket": "^4.2.0",
"recharts": "^2.1.12" "recharts": "^2.1.12"
}, },
"scripts": { "scripts": {
......
frontend/public/favicon.ico

3.08 KiB

...@@ -2,19 +2,13 @@ ...@@ -2,19 +2,13 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/thumbnail_violet.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"
content="Web site created using create-react-app" content="Web site developed by ViaRézo"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.
...@@ -24,7 +18,7 @@ ...@@ -24,7 +18,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>React App</title> <title>Eatfast</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
......
frontend/public/logo192.png

5.22 KiB

frontend/public/logo512.png

9.44 KiB

{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
frontend/public/thumbnail_violet.png

86.7 KiB