From 0085f10b90dc2cbafbd5cc515f1aeb6b95d2fbc7 Mon Sep 17 00:00:00 2001
From: Antoine Gaudron-desjardins <antoine.gaudrondesjardins@student-cs.fr>
Date: Thu, 7 Jul 2022 18:57:51 +0200
Subject: [PATCH] restructure backend

---
 backend/db/crud.py          | 74 +++++++++++++++++++++++++++++--------
 backend/db/database.py      |  8 ++++
 backend/db/models.py        | 24 +++++++++++-
 backend/db/schemas.py       | 40 +++++++++++++++++---
 backend/main.py             | 40 ++++----------------
 backend/requirements.txt    |  3 +-
 backend/routers/__init__.py |  0
 backend/routers/comments.py | 24 ++++++++++++
 backend/routers/news.py     | 24 ++++++++++++
 backend/routers/stats.py    | 21 +++++++++++
 10 files changed, 204 insertions(+), 54 deletions(-)
 create mode 100644 backend/routers/__init__.py
 create mode 100644 backend/routers/comments.py
 create mode 100644 backend/routers/news.py
 create mode 100644 backend/routers/stats.py

diff --git a/backend/db/crud.py b/backend/db/crud.py
index fe13875..c460f2e 100644
--- a/backend/db/crud.py
+++ b/backend/db/crud.py
@@ -2,27 +2,14 @@
 Module to interact with the database
 """
 from datetime import date, datetime, time, timedelta
-from numpy import average
 from sqlalchemy.orm import Session
 from sqlalchemy.sql import func
+import pytz
 
 from db import models, schemas
 
 
-def get_records(place: str, db: Session):
-    """ Get all the records for the given place """
-    records = db.query(models.Records).filter(models.Records.place == place).order_by(models.Records.date.desc()).all()
-    return records
-
-
-def create_record(new_record: schemas.RecordBase, db: Session):
-    """ Add a new record to the database """
-    db_record = models.Records(**new_record.dict())
-    db.add(db_record)
-    db.commit()
-    db.refresh(db_record)
-    return db_record
-
+## Define CRUD operation to collect the statistics
 
 def get_waiting_time(place: str, db: Session):
     """ Get the last estimated waiting time for the given place """
@@ -72,3 +59,60 @@ def get_stats(place: str, weekday: int, min_time_hour: int, min_time_mn: int, ma
         start_time, end_time = end_time, shift_time(end_time, interval)
 
     return stats
+
+
+## Define CRUD operation for the comments
+
+def get_comments(place: str, page: int, db: Session):
+    """ Get  the 10 last comments for the given place """
+    if page == 0:
+        comments = db.query(models.Comments).order_by(models.Comments.date.desc(), models.Comments.id.desc()).all()
+    else:
+        comments = db.query(models.Comments).filter(models.Comments.place == place).order_by(models.Comments.date.desc(), models.Comments.id.desc()).slice((page-1)*10, page*10).all()
+    return comments
+
+
+def create_comment(place: str, new_comments: schemas.CommentBase, db: Session):
+    """ Add a new comment to the database """
+    date = datetime.now(tz=pytz.timezone("Europe/Paris"))
+    db_comment = models.Comments(**new_comments.dict(), date=date, place=place)
+    db.add(db_comment)
+    db.commit()
+    db.refresh(db_comment)
+    return db_comment
+
+
+def delete_comment(id: int, db: Session):
+    """ Delete the comment with the matching id """
+    if id == 0:
+        db.query(models.Comments).delete()
+    else:
+        db.query(models.Comments).filter(models.Comments.id == id).delete()
+    db.commit()
+
+
+## Define CRUD operation for the news
+
+def get_news(place: str, db: Session):
+    """ Get the news for the given place """
+    news = db.query(models.News).filter(models.News.place == place).order_by(models.News.date.desc()).all()
+    return news
+
+
+def create_news(new_news: schemas.NewsBase, db: Session):
+    """ Add a news to the database """
+    date = datetime.now(tz=pytz.timezone("Europe/Paris"))
+    db_news = models.News(**new_news.dict(), published_at=date)
+    db.add(db_news)
+    db.commit()
+    db.refresh(db_news)
+    return db_news
+
+
+def delete_news(id: int, db: Session):
+    """ Delete the news with the matching id """
+    if id == 0:
+        db.query(models.News).delete()
+    else:
+        db.query(models.News).filter(models.News.id == id).delete()
+    db.commit()
\ No newline at end of file
diff --git a/backend/db/database.py b/backend/db/database.py
index 4ea6517..eed306e 100644
--- a/backend/db/database.py
+++ b/backend/db/database.py
@@ -21,3 +21,11 @@ engine = create_engine(SQLALCHEMY_DATABASE_URL)
 SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
 
 Base = declarative_base()
+
+def get_db():
+    """Create a database session."""
+    db = SessionLocal()
+    try:
+        yield db
+    finally:
+        db.close()
\ No newline at end of file
diff --git a/backend/db/models.py b/backend/db/models.py
index 4971ef3..898bf95 100644
--- a/backend/db/models.py
+++ b/backend/db/models.py
@@ -1,7 +1,7 @@
 """
 Models of the database for magasin app
 """
-from sqlalchemy import Column, Integer, DateTime, Float, Interval, String
+from sqlalchemy import Column, Integer, DateTime, Float, Interval, String, Text
 
 from db.database import Base
 
@@ -15,3 +15,25 @@ class Records(Base):
     date = Column(DateTime)
     density = Column(Float)
     waiting_time = Column(Interval)
+
+
+class Comments(Base):
+    """Comments sql table model"""
+    __tablename__ = "comments"
+
+    id = Column(Integer, primary_key=True, index=True)
+    comment = Column(Text)
+    date = Column(DateTime)
+    place = Column(String(10))
+
+
+class News(Base):
+    """Records sql table model"""
+    __tablename__ = "News sql table model"
+
+    id = Column(Integer, primary_key=True, index=True)
+    title = Column(String(50))
+    content = Column(Text)
+    published_at = Column(DateTime)
+    end_date = Column(DateTime)
+    place = Column(String(10))
diff --git a/backend/db/schemas.py b/backend/db/schemas.py
index 5f10ba9..edb7b33 100644
--- a/backend/db/schemas.py
+++ b/backend/db/schemas.py
@@ -8,17 +8,47 @@ from pydantic import BaseModel, Field
 
 class RecordBase(BaseModel):
     """Records base schema"""
-    place: str = Field(...,
-                       title="Name of the RU corresponding the given record")
+    place: str = Field(..., title="Name of the RU corresponding the given record")
     date: datetime = Field(..., title="Date of the record")
     density: float = Field(..., title="Estimated density of people")
-    waiting_time: Optional[timedelta] = Field(
-        title="Estimated waiting time for people coming at this date")
+    waiting_time: Optional[timedelta] = Field(title="Estimated waiting time for people coming at this date")
 
 
 class Record(RecordBase):
-    """Database records schema"""
+    """Database records base schema"""
     id: int
 
     class Config:
         orm_mode = True
+
+
+class CommentBase(BaseModel):
+    """Comments base schema"""
+    comment: str = Field(..., title="Content of the comment posted")
+
+
+class Comment(CommentBase):
+    """Database comments base schema"""
+    id: int
+    date: datetime = Field(..., title="Publication date of the comment")
+    place: str = Field(..., title="Name of the RU corresponding the comment")
+
+    class Config:
+        orm_mode = True
+
+
+class NewsBase(BaseModel):
+    """News sql table model"""
+    title: str = Field(..., title="Title of the news")
+    content: str = Field(..., title="Content of the news")
+    end_date: datetime = Field(..., title="End date to display the news")
+    place: str = Field(..., title="Name of the RU corresponding the news")
+
+
+class News(NewsBase):
+    """Database news base schema"""
+    id: int
+    published_at: datetime = Field(..., title="Publication date of the news")
+
+    class Config:
+        orm_mode = True
\ No newline at end of file
diff --git a/backend/main.py b/backend/main.py
index fc29c12..21db8ec 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -1,19 +1,16 @@
-from datetime import datetime, time, timedelta
-from typing import List
-from fastapi import Body, Depends, FastAPI
+from fastapi import FastAPI
 from fastapi.middleware.cors import CORSMiddleware
-from sqlalchemy.orm import Session
 from dotenv import load_dotenv
 import os
 
-from db import crud, schemas, database, models
+from db import database, models
+from routers import stats, comments, news
 
+app = FastAPI(docs_url="/api/docs", openapi_url="/api/openapi.json")
 
 # load environment variables
 load_dotenv()
 
-app = FastAPI(docs_url="/api/docs", openapi_url="/api/openapi.json")
-
 origins = [
     os.getenv('WEB_ROOT'),
 ]
@@ -27,40 +24,19 @@ app.add_middleware(
 )
 
 
-def get_db():
-    """Create a database session."""
-    db = database.SessionLocal()
-    try:
-        yield db
-    finally:
-        db.close()
-
-
 @app.on_event("startup")
 def on_startup():
     # Database creation
     models.Base.metadata.create_all(bind=database.engine)
 
 
-@app.get('/api/{place}', response_model=List[schemas.Record])
-async def eatfast(place: str, db: Session = Depends(get_db)):
-    return crud.get_records(place, db)
-
-
-@app.post('/api/create', response_model=schemas.Record)
-async def post(record: schemas.RecordBase = Body(...), db: Session = Depends(get_db)):
-    return crud.create_record(record, db)
-
+# Integration of routers
+app.include_router(stats.router)
+app.include_router(comments.router)
+app.include_router(news.router)
 
-@app.get('/api/{place}/waiting_time', response_model=timedelta)
-async def waiting_time(place: str, db: Session = Depends(get_db)):
-    return crud.get_waiting_time(place, db)
 
 
-@app.get('/api/{place}/stats/{day}/{min_time_hour}/{min_time_mn}/{max_time_hour}/{max_time_mn}/{interval}', response_model=list)
-async def stats(place: str, day: int, min_time_hour: int, min_time_mn: int,
-                max_time_hour: int, max_time_mn: int, interval: timedelta, db: Session = Depends(get_db)):
-    return crud.get_stats(place, day, min_time_hour, min_time_mn, max_time_hour, max_time_mn, interval, db)
 
 
 """
diff --git a/backend/requirements.txt b/backend/requirements.txt
index dd60603..eb0a04d 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -14,4 +14,5 @@ typing-extensions==4.2.0
 uvicorn==0.17.6
 SQLAlchemy==1.4.19
 python-dotenv==0.18.0
-PyMySQL==1.0.2
\ No newline at end of file
+PyMySQL==1.0.2
+pytz==2022.1
\ No newline at end of file
diff --git a/backend/routers/__init__.py b/backend/routers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/routers/comments.py b/backend/routers/comments.py
new file mode 100644
index 0000000..ee7ce9d
--- /dev/null
+++ b/backend/routers/comments.py
@@ -0,0 +1,24 @@
+from fastapi import APIRouter, 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/comments", tags=["comments"])
+
+
+@router.get('/{place}', response_model=List[schemas.Comment])
+async def get_comments(place: str, page: int=1, db: Session = Depends(get_db)):
+    return crud.get_comments(place, page, db)
+
+
+@router.post('/{place}', response_model=schemas.Comment)
+async def create_comment(place: str, comment: schemas.CommentBase, db: Session = Depends(get_db)):
+    return crud.create_comment(place, comment, db)
+
+
+@router.delete('/{id}', response_model=None)
+async def delete_comment(id: int, db: Session = Depends(get_db)):
+    return crud.delete_comment(id, db)
\ No newline at end of file
diff --git a/backend/routers/news.py b/backend/routers/news.py
new file mode 100644
index 0000000..4d0bd3e
--- /dev/null
+++ b/backend/routers/news.py
@@ -0,0 +1,24 @@
+from fastapi import APIRouter, 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/news", tags=["news"])
+
+
+@router.get('/{place}', response_model=List[schemas.News])
+async def get_news(place: str, db: Session = Depends(get_db)):
+    return crud.get_news(place, db)
+
+
+@router.post('/{place}', response_model=schemas.News)
+async def create_news(place: str, news: schemas.NewsBase, db: Session = Depends(get_db)):
+    return crud.create_news(place, news, db)
+
+
+@router.delete('/{id}', response_model=None)
+async def delete_news(id: int, db: Session = Depends(get_db)):
+    return crud.delete_news(id, db)
\ No newline at end of file
diff --git a/backend/routers/stats.py b/backend/routers/stats.py
new file mode 100644
index 0000000..69bc337
--- /dev/null
+++ b/backend/routers/stats.py
@@ -0,0 +1,21 @@
+from fastapi import APIRouter, Depends
+from sqlalchemy.orm import Session
+from datetime import timedelta
+from typing import List
+
+from db import crud
+from db.database import get_db
+
+
+router = APIRouter(prefix="/api", tags=["stats"])
+
+
+@router.get('/{place}/waiting_time', response_model=timedelta)
+async def waiting_time(place: str, db: Session = Depends(get_db)):
+    return crud.get_waiting_time(place, db)
+
+
+@router.get('/{place}/stats/{day}/{min_time_hour}/{min_time_mn}/{max_time_hour}/{max_time_mn}/{interval}', response_model=list)
+async def stats(place: str, day: int, min_time_hour: int, min_time_mn: int,
+                max_time_hour: int, max_time_mn: int, interval: timedelta, db: Session = Depends(get_db)):
+    return crud.get_stats(place, day, min_time_hour, min_time_mn, max_time_hour, max_time_mn, interval, db)
\ No newline at end of file
-- 
GitLab