Add flight update validations

Also, fix some bugs
This commit is contained in:
Santiago Lo Coco 2023-11-04 20:27:41 -03:00
parent 39822ca6a6
commit a82840e0bf
13 changed files with 7648 additions and 67 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -7,12 +7,8 @@
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
content="Airport browser"
/>
<!--
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" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
@ -20,28 +16,10 @@
href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap"
rel="stylesheet"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
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`.
-->
<title>Client Users</title>
<title>Airport browser</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
--></body>
</body>
</html>

3748
db.sql Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
from datetime import datetime, timedelta
from sqlalchemy import Date
from sqlalchemy.orm import Session
from sqlalchemy.sql import func
@ -6,6 +8,53 @@ from src.api.models.flight import Flight
from src.api.schemas.flight import Flight as FlightPydantic
def is_flight_unique(db: Session, flight: FlightPydantic, id: int = None):
return (
db.query(Flight)
.filter(
Flight.id != id if id else True,
Flight.flight_code == flight.flight_code,
Flight.origin == flight.origin,
Flight.destination == flight.destination,
Flight.departure_time == flight.departure_time,
Flight.arrival_time == flight.arrival_time,
)
.count()
== 0
)
def is_flight_collision(db: Session, flight: Flight, id: int = None):
if not flight.gate:
return False
if not isinstance(flight.departure_time, datetime):
setattr(
flight,
"departure_time",
datetime.strptime(flight.departure_time, "%Y-%m-%d %I:%M %p"),
)
time_window = timedelta(minutes=30)
departure_time_lower_limit = flight.departure_time - time_window
departure_time_upper_limit = flight.departure_time + time_window
collision_count = (
db.query(Flight)
.filter(
Flight.id != id if id else True,
Flight.gate == flight.gate,
Flight.origin == flight.origin,
Flight.departure_time.between(
departure_time_lower_limit, departure_time_upper_limit
),
)
.count()
)
return collision_count > 0
def get_flight_by_id(db: Session, flight_id: int):
return db.query(Flight).filter(Flight.id == flight_id).first()
@ -15,6 +64,9 @@ def get_flights(db: Session, skip: int = 0, limit: int = 100):
def create_flight(db: Session, flight: FlightPydantic):
if not is_flight_unique(db, flight):
raise ValueError
db_flight = Flight(
flight_code=flight.flight_code,
status=flight.status,
@ -45,6 +97,49 @@ def update_flight_status(db: Session, status, id):
return db_flight
def update_flight(db: Session, update_data, id):
db_flight = db.query(Flight).filter(Flight.id == id).first()
if db_flight is None:
raise KeyError
print(update_data)
if db_flight.user_id != update_data["user_id"]:
raise PermissionError
new_flight = Flight(
**{
key: value
for key, value in {**db_flight.__dict__, **update_data}.items()
if not key.startswith("_")
}
)
if (
new_flight.flight_code != db_flight.flight_code
or new_flight.destination != db_flight.destination
or new_flight.origin != db_flight.origin
or new_flight.departure_time != db_flight.departure_time
or new_flight.arrival_time != db_flight.arrival_time
) and not is_flight_unique(db, new_flight, id):
raise ValueError("non-unique")
if (
new_flight.destination != db_flight.destination
or new_flight.origin != db_flight.origin
or new_flight.departure_time != db_flight.departure_time
or new_flight.arrival_time != db_flight.arrival_time
or new_flight.gate != db_flight.gate
) and is_flight_collision(db, new_flight, id):
raise ValueError("collision")
for key, value in update_data.items():
setattr(db_flight, key, value)
setattr(db_flight, "last_updated", func.now())
db.commit()
db.refresh(db_flight)
return db_flight
def get_flights_by_origin(db: Session, origin: str, future: str):
if future:
return (

View File

@ -7,7 +7,7 @@ from sqlalchemy.orm import Session
from src.api.config import API_MESSAGES
from src.api.cruds import flight as flight_crud
from src.api.db import get_db
from src.api.schemas.flight import Flight, FlightCreate, FlightStatusUpdate
from src.api.schemas.flight import Flight, FlightCreate, FlightUpdate
router = APIRouter()
@ -22,24 +22,39 @@ def get_flight_by_id(id: int, db: Session = Depends(get_db)):
@router.post("", response_model=Flight)
def create_flight(flight: FlightCreate, db: Session = Depends(get_db)):
return flight_crud.create_flight(db=db, flight=flight)
try:
return flight_crud.create_flight(db=db, flight=flight)
except ValueError:
raise HTTPException(status_code=409, detail="Flight already exists")
@router.patch("/{id}", response_model=Flight)
async def update_flight(
id: int,
status: FlightStatusUpdate,
update: FlightUpdate,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
):
try:
db_flight = flight_crud.update_flight_status(db=db, id=id, status=status)
update_data = {
key: value
for key, value in update.model_dump().items()
if value is not None
}
print(update_data)
db_flight = flight_crud.update_flight(db=db, id=id, update_data=update_data)
except PermissionError:
raise HTTPException(status_code=401, detail="Unauthorized")
except KeyError:
raise HTTPException(status_code=404, detail="Flight not found")
except ValueError as e:
msg = str(e)
if msg == "non-unique":
raise HTTPException(status_code=409, detail="Flight already exists")
elif msg == "collision":
raise HTTPException(status_code=409, detail="Flight collision")
msg = status.model_dump()
msg = update.model_dump()
msg["id"] = id
msg["flight_code"] = db_flight.flight_code
msg["origin"] = db_flight.origin

View File

@ -1,4 +1,5 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, validator
@ -13,9 +14,7 @@ class Flight(BaseModel):
arrival_time: str
gate: str = None
user_id: int
# last_updated: str
# @validator("departure_time", "arrival_time", "last_updated", pre=True, always=True)
@validator("departure_time", "arrival_time", pre=True, always=True)
def parse_datetime(cls, value):
if isinstance(value, datetime):
@ -37,3 +36,14 @@ class FlightCreate(BaseModel):
class FlightStatusUpdate(BaseModel):
status: str
user_id: int
class FlightUpdate(BaseModel):
flight_code: Optional[str] = None
status: Optional[str] = None
origin: Optional[str] = None
destination: Optional[str] = None
departure_time: Optional[str] = None
arrival_time: Optional[str] = None
gate: Optional[str] = None
user_id: int

View File

@ -5,7 +5,7 @@ from fastapi import APIRouter, Header, HTTPException
from src.api.config import API_FLIGHTS
from src.api.routes.auth import status as checkAuth
from src.api.schemas.flight import Flight, FlightCreate, FlightStatusUpdate
from src.api.schemas.flight import Flight, FlightCreate, FlightUpdate
router = APIRouter()
@ -34,13 +34,13 @@ async def create_flight(
@router.patch("/{id}", response_model=Flight)
async def update_flight(
id: int,
status_update: FlightStatusUpdate,
flight_update: FlightUpdate,
authorization: Annotated[str | None, Header()] = None,
):
auth = await checkAuth(authorization)
status = status_update.model_dump()
status["user_id"] = auth["id"]
(response, status, _) = await request(f"{API_FLIGHTS}/{id}", "PATCH", json=status)
update = flight_update.model_dump()
update["user_id"] = auth["id"]
(response, status, _) = await request(f"{API_FLIGHTS}/{id}", "PATCH", json=update)
if status < 200 or status > 204:
raise HTTPException(status_code=status, detail=response)
return response

View File

@ -1,4 +1,5 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, validator
@ -32,3 +33,13 @@ class FlightCreate(BaseModel):
class FlightStatusUpdate(BaseModel):
status: str
class FlightUpdate(BaseModel):
flight_code: Optional[str] = None
status: Optional[str] = None
origin: Optional[str] = None
destination: Optional[str] = None
departure_time: Optional[str] = None
arrival_time: Optional[str] = None
gate: Optional[str] = None

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -7,12 +7,8 @@
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
content="Airport screen"
/>
<!--
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" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
@ -20,28 +16,10 @@
href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap"
rel="stylesheet"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
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`.
-->
<title>Client Users</title>
<title>Airport screen</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
--></body>
</body>
</html>

View File

@ -49,7 +49,6 @@ export const Arrival: React.FC<Props> = (props) => {
<Td>{flight.flight_code}</Td>
<Td>{flight.origin}</Td>
<Td>{flight.arrival_time}</Td>
<Td>{flight.gate}</Td>
<Td>{flight.status}</Td>
</Tr>
))}
@ -62,7 +61,6 @@ export const Arrival: React.FC<Props> = (props) => {
<Td></Td>
<Td></Td>
<Td></Td>
<Td></Td>
</Tr>
)
})}

View File

@ -13,8 +13,6 @@ code {
}
.App {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;