Skip to content
This repository has been archived by the owner on Aug 12, 2024. It is now read-only.

ADA Requests MVP #137

Merged
merged 15 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions backend/alembic/versions/38c5326f0334_create_pickup_spots_table.py
OxygenCobalt marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""create pickup spots table

Revision ID: 38c5326f0334
Revises: 1a8780dd5bbc
Create Date: 2024-02-22 10:17:36.297413

"""

from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "38c5326f0334"
down_revision: Union[str, None] = "1a8780dd5bbc"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.create_table(
"pickup_spots",
sa.Column("id", sa.Integer, primary_key=True),
sa.Column("name", sa.String(255), nullable=False),
sa.UniqueConstraint("name"),
)


def downgrade() -> None:
op.execute(
"""
DROP TABLE IF EXISTS public.pickup_spots;
"""
)
41 changes: 41 additions & 0 deletions backend/alembic/versions/8166e12f260c_create_ada_requests_table.py
OxygenCobalt marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""create ada requests table

Revision ID: 8166e12f260c
Revises: 38c5326f0334
Create Date: 2024-02-22 10:38:27.424804

"""

from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "8166e12f260c"
down_revision: Union[str, None] = "38c5326f0334"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.create_table(
"ada_requests",
sa.Column("id", sa.Integer, primary_key=True),
sa.Column(
"pickup_spot",
sa.Integer,
sa.ForeignKey("pickup_spots.id"),
nullable=False,
),
sa.Column("created_at", sa.TIMESTAMP(timezone=True), nullable=False),
sa.Column("wheelchair", sa.Boolean, nullable=False),
)


def downgrade() -> None:
op.execute(
"""
DROP TABLE IF EXISTS public.ada_requests;
"""
)
150 changes: 150 additions & 0 deletions backend/src/handlers/ada.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
from datetime import datetime, timezone
from typing import Annotated, Dict, List, Union

from fastapi import APIRouter, HTTPException, Query, Request
from pydantic import BaseModel
from src.model.ada_request import ADARequest
from src.model.pickup_spot import PickupSpot
from src.request import process_include

router = APIRouter(prefix="/ada", tags=["ada"])

FIELD_PICKUP_SPOTS = "pickup_spot"
INCLUDES = {FIELD_PICKUP_SPOTS}


class PickupSpotModel(BaseModel):
name: str
OxygenCobalt marked this conversation as resolved.
Show resolved Hide resolved


@router.get("/pickup_spots")
def get_pickup_spots(req: Request) -> List[Dict[str, Union[str, int]]]:
with req.app.state.db.session() as session:
pickup_spots = session.query(PickupSpot).all()
pickup_spots_json: List[Dict[str, Union[str, int]]] = []
for spot in pickup_spots:
spot_json = {
"id": spot.id,
"name": spot.name,
}
pickup_spots_json.append(spot_json)

return pickup_spots_json


@router.post("/pickup_spots")
def post_pickup_spot(spot: PickupSpotModel, req: Request):
new_spot = PickupSpot(name=spot.name)
OxygenCobalt marked this conversation as resolved.
Show resolved Hide resolved

with req.app.state.db.session() as session:
session.add(new_spot)
session.commit()

return {"message": "OK"}


@router.put("/pickup_spots/{id}")
def update_pickup_spot(id: int, spot: PickupSpotModel, req: Request):
with req.app.state.db.session() as session:
pickup_spot = session.query(PickupSpot).filter(PickupSpot.id == id).first()

if pickup_spot is None:
raise HTTPException(status_code=404, detail="Pickup spot not found")

pickup_spot.name = spot.name
OxygenCobalt marked this conversation as resolved.
Show resolved Hide resolved
session.commit()

return {"message": "OK"}


@router.delete("/pickup_spots/{id}")
def delete_pickup_spot(id: int, req: Request):
with req.app.state.db.session() as session:
pickup_spot = session.query(PickupSpot).filter(PickupSpot.id == id).first()

if pickup_spot is None:
raise HTTPException(status_code=404, detail="Pickup spot not found")

session.query(PickupSpot).filter(PickupSpot.id == id).delete()
session.commit()

return {"message": "OK"}


class ADARequestModel(BaseModel):
pickup_spot_id: int
pickup_time: int
wheelchair: bool


@router.get("/requests")
def get_ada_requests(
req: Request,
filter: str = Query("future"),
include: Annotated[list[str] | None, Query()] = None,
):
now = datetime.now(timezone.utc)
include_set = process_include(include, INCLUDES)
with req.app.state.db.session() as session:
if filter == "today":
end_of_day = datetime.now(timezone.utc).replace(
hour=23, minute=59, second=59
)
ada_requests = session.query(ADARequest).filter(
ADARequest.created_at >= now, ADARequest.created_at <= end_of_day
)
elif filter == "future":
ada_requests = session.query(ADARequest).filter(
ADARequest.created_at >= now
)
else:
raise HTTPException(status_code=400, detail=f"Invalid filter {filter}")
OxygenCobalt marked this conversation as resolved.
Show resolved Hide resolved

ada_requests = ada_requests.all()

result = []
for request in ada_requests:
request_json = {
"id": request.id,
"pickup_time": int(request.created_at.timestamp()),
"wheelchair": request.wheelchair,
}
if FIELD_PICKUP_SPOTS in include_set:
spot = (
session.query(PickupSpot)
.filter(PickupSpot.id == request.pickup_spot)
.first()
)
request_json[FIELD_PICKUP_SPOTS] = {"id": spot.id, "name": spot.name}
result.append(request_json)

return result


@router.post("/requests")
def create_ada_request(
req: Request, ada_request_model: ADARequestModel
) -> dict[str, str]:
pickup_time = datetime.fromtimestamp(ada_request_model.pickup_time, timezone.utc)
# Make sure that the pickup time is in the future
if pickup_time <= datetime.now(timezone.utc):
raise HTTPException(status_code=400, detail="Pickup time must be in the future")

with req.app.state.db.session() as session:
pickup_spot = (
session.query(PickupSpot)
.filter(PickupSpot.id == ada_request_model.pickup_spot_id)
.count()
)
if not pickup_spot:
raise HTTPException(status_code=400, detail="Pickup spot not found")

pickup_spot = ADARequest(
pickup_spot=ada_request_model.pickup_spot_id,
wheelchair=ada_request_model.wheelchair,
created_at=pickup_time,
OxygenCobalt marked this conversation as resolved.
Show resolved Hide resolved
)
session.add(pickup_spot)
session.commit()

return {"message": "OK"}
3 changes: 2 additions & 1 deletion backend/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from fastapi.middleware.cors import CORSMiddleware

from .db import DBWrapper
from .handlers import alert, ridership, routes, stops, vans
from .handlers import ada, alert, ridership, routes, stops, vans
from .hardware import HardwareExceptionMiddleware
from .vantracking.factory import van_tracker
from .vantracking.tracker import VanTracker
Expand All @@ -21,6 +21,7 @@
)

app.add_middleware(HardwareExceptionMiddleware)
app.include_router(ada.router)
app.include_router(routes.router)
app.include_router(stops.router)
app.include_router(alert.router)
Expand Down
29 changes: 29 additions & 0 deletions backend/src/model/ada_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from datetime import datetime

from sqlalchemy import ForeignKeyConstraint
from sqlalchemy.orm import Mapped, mapped_column
from src.db import Base
from src.model.types import TZDateTime


class ADARequest(Base):
__tablename__ = "ada_requests"
__table_args__ = (ForeignKeyConstraint(["pickup_spot"], ["pickup_spots.id"]),)
id: Mapped[int] = mapped_column(
primary_key=True, autoincrement=True, nullable=False
)
pickup_spot: Mapped[int] = mapped_column(nullable=False)
created_at: Mapped[datetime] = mapped_column(TZDateTime, nullable=False)
OxygenCobalt marked this conversation as resolved.
Show resolved Hide resolved
wheelchair: Mapped[bool] = mapped_column(nullable=False)

def __eq__(self, __value: object) -> bool:
# Exclude ID since it'll always differ, only compare on content
return (
isinstance(__value, ADARequest)
and self.pickup_spot == __value.pickup_spot
and self.created_at == __value.created_at
and self.wheelchair == __value.wheelchair
)

def __repr__(self) -> str:
return f"<AdaRequest id={self.id} pickup_spot={self.pickup_spot} created_at={self.created_at} wheelchair={self.wheelchair}>"
19 changes: 19 additions & 0 deletions backend/src/model/pickup_spot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from src.db import Base


class PickupSpot(Base):
__tablename__ = "pickup_spots"
__table_args__ = (UniqueConstraint("name"),)
id: Mapped[int] = mapped_column(
primary_key=True, autoincrement=True, nullable=False
)
name: Mapped[str] = mapped_column(unique=True, nullable=False)

def __eq__(self, __value: object) -> bool:
# Exclude ID since it'll always differ, only compare on content
return isinstance(__value, PickupSpot) and self.name == __value.name

def __repr__(self) -> str:
return f"<PickupSpot id={self.id} name={self.name}>"
38 changes: 38 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading