diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fdcb694..8103b4c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -450,8 +450,6 @@ deploy-prod: - export API_IMAGE=$DOCKER_HUB_USER_MANAGER_IMAGE - export FOLDER=auth-domain - *stop-and-run - - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_PROD_FILE exec auth-api python manage.py recreate_db - - docker compose -f auth-domain/docker-compose.yml --env-file $ENV_PROD_FILE exec auth-api python manage.py seed_db - export API_IMAGE=$DOCKER_HUB_SUBSCRIPTION_IMAGE - export FOLDER=subscription-domain diff --git a/browser-domain/src/Api.ts b/browser-domain/src/Api.ts index 027d366..e0801c9 100644 --- a/browser-domain/src/Api.ts +++ b/browser-domain/src/Api.ts @@ -17,6 +17,14 @@ instance.interceptors.request.use((request) => { instance.interceptors.response.use( (response) => { + console.log(response.headers) + if (response.headers["x-count"]) { + let json: any = {} + json["flights"] = JSON.parse(response.data); + json["count"] = response.headers["x-count"] + console.log(json) + return json + } return JSON.parse(response.data); }, (error) => { @@ -51,8 +59,13 @@ export const tokenStatus = ( }); }; -export const fetchZones = (origin: string | null): Promise => { - return instance.get("flights" + (origin ? "?origin=" + origin : "")) +interface FlightData { + flights: Flight[] + count: number +} + +export const fetchFlights = (origin: string | null, page: number | null): Promise => { + return instance.get("flights" + (origin ? "?origin=" + origin : "") + (page ? "?page=" + page : "")) }; export const createFlight = ( diff --git a/browser-domain/src/components/CreateFlight/CreateFlight.tsx b/browser-domain/src/components/CreateFlight/CreateFlight.tsx index 3186794..fd6fcc0 100644 --- a/browser-domain/src/components/CreateFlight/CreateFlight.tsx +++ b/browser-domain/src/components/CreateFlight/CreateFlight.tsx @@ -11,9 +11,9 @@ export const CreateFlight = () => { const [flightData, setFlightData] = useState({ flight_code: "ABC123", - status: "En ruta", - origin: "Ciudad A", - destination: "Ciudad B", + status: "Scheduled", + origin: "Frankfurt", + destination: "Rome", departure_time: "2023-10-09 10:00 AM", arrival_time: "2023-10-09 12:00 PM", gate: "A1", diff --git a/browser-domain/src/components/Home/Card/Card.css b/browser-domain/src/components/Home/Card/Card.css index 0f52e3c..2040ed2 100644 --- a/browser-domain/src/components/Home/Card/Card.css +++ b/browser-domain/src/components/Home/Card/Card.css @@ -1,8 +1,7 @@ .flight-card { display: flex; flex-direction: column; - justify-content: space-between; - align-items: flex-start; + flex-wrap: wrap; padding: 16px; border: 1px solid #ddd; border-radius: 8px; diff --git a/browser-domain/src/components/Home/Card/Card.tsx b/browser-domain/src/components/Home/Card/Card.tsx index 6832893..0a78378 100644 --- a/browser-domain/src/components/Home/Card/Card.tsx +++ b/browser-domain/src/components/Home/Card/Card.tsx @@ -24,20 +24,17 @@ export const Card: React.FC = ({ flight }) => { return (
- } />
- {flight.flight_code} -
- - {flight.origin} {flight.destination} - -
+ {flight.flight_code} + + {flight.origin} {flight.destination} +
Status: - {flight.status} + {flight.status} Departure: diff --git a/browser-domain/src/components/Home/Home.tsx b/browser-domain/src/components/Home/Home.tsx index 67566b6..8649271 100644 --- a/browser-domain/src/components/Home/Home.tsx +++ b/browser-domain/src/components/Home/Home.tsx @@ -1,6 +1,6 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { Card } from "./Card/Card"; -import { useFetchZones } from "../../hooks/useFetchZones"; +import { useFetchFlights } from "../../hooks/useFetchFlights"; import { Flight } from "../../Types"; import { useNavigate } from "react-router"; import useAuth from "../../useAuth"; @@ -12,25 +12,56 @@ interface Props { export const Home: React.FC = (props) => { const urlParams = new URLSearchParams(window.location.search); const origin = urlParams.get('origin'); - const { zones, error } = useFetchZones(origin); + const initialPage = parseInt(urlParams.get('page') || '1', 10); + const { flights, count, error } = useFetchFlights(origin, initialPage); const navigate = useNavigate() + const [currentPage, setCurrentPage] = useState(initialPage); const { loading, isAirline } = useAuth(); + useEffect(() => { + const newParams = new URLSearchParams(window.location.search); + newParams.set('page', currentPage.toString()); + navigate(`?${newParams.toString()}`); + }, [currentPage, navigate]); + + const goToPrevPage = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + } + }; + + const goToNextPage = () => { + setCurrentPage(currentPage + 1); + }; + + const checkMaxPage = () => { + return currentPage * 8 >= count ? false : true + } + + const checkMinPage = () => { + return currentPage > 1 ? true : false + } + if (loading) { return
Loading...
; } return (
- {isAirline ? : <>} + {isAirline ? : <>}

Flights

- {(props.flights ? props.flights : zones).map((u) => { + {(props.flights ? props.flights : flights).map((u) => { return ; })} {error ?
{error}
: <>}
+
+ {checkMinPage() && } + Page {currentPage} + {checkMaxPage() && } +
); }; diff --git a/browser-domain/src/hooks/useFetchFlights.tsx b/browser-domain/src/hooks/useFetchFlights.tsx new file mode 100644 index 0000000..d72f228 --- /dev/null +++ b/browser-domain/src/hooks/useFetchFlights.tsx @@ -0,0 +1,23 @@ +import React, { useEffect } from "react"; +import { useState } from "react"; +import { User, Flight } from "../Types"; +import { fetchFlights } from "../Api"; + +export const useFetchFlights = (origin: string | null, page: number | null) => { + const [error, setError] = useState(null); + const [flights, setFlights] = useState([]); + const [count, setCount] = useState(0); + + useEffect(() => { + setError(null); + + fetchFlights(origin, page) + .then((data) => { + setCount(data.count) + setFlights(data.flights); + }) + .catch((error) => { }); + }, [page]); + + return { flights, count, error }; +}; diff --git a/browser-domain/src/hooks/useFetchZones.tsx b/browser-domain/src/hooks/useFetchZones.tsx deleted file mode 100644 index 5e3df8b..0000000 --- a/browser-domain/src/hooks/useFetchZones.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { useEffect } from "react"; -import { useState } from "react"; -import { User, Flight } from "../Types"; -import { fetchZones } from "../Api"; - -export const useFetchZones = (origin: string | null) => { - const [error, setError] = useState(null); - const [zones, setZones] = useState([]); - - useEffect(() => { - setError(null); - - fetchZones(origin) - .then((data) => { - setZones(data); - }) - .catch((error) => { }); - }, []); - - return { zones, error }; -}; diff --git a/browser-domain/src/index.css b/browser-domain/src/index.css index a94edcd..f9e676d 100644 --- a/browser-domain/src/index.css +++ b/browser-domain/src/index.css @@ -85,22 +85,7 @@ code { } .Items { - height: 100%; - width: 100%; - display: flex; - flex-wrap: wrap; - justify-content: space-between; - align-items: center; + display: grid; + grid-template-columns: repeat(4, minmax(80px, 250px)); gap: 20px; -} - -.List { - width: 100%; - height: 500px; - gap: 30px; - padding: 20px; - overflow-y: auto; - display: flex; - align-items: center; - flex-direction: column; } \ No newline at end of file diff --git a/flights-domain/flights-information/src/api/cruds/flight.py b/flights-domain/flights-information/src/api/cruds/flight.py index 9c947e1..5c8b22a 100644 --- a/flights-domain/flights-information/src/api/cruds/flight.py +++ b/flights-domain/flights-information/src/api/cruds/flight.py @@ -59,8 +59,12 @@ def get_flight_by_id(db: Session, flight_id: int): return db.query(Flight).filter(Flight.id == flight_id).first() -def get_flights(db: Session, skip: int = 0, limit: int = 100): - return db.query(Flight).offset(skip).limit(limit).all() +def get_flights(db: Session, page: int = 1, limit: int = 8): + if page <= 0: + page = 1 + skip = (page - 1) * limit + count = db.query(Flight).count() + return db.query(Flight).offset(skip).limit(limit).all(), count def create_flight(db: Session, flight: FlightPydantic): diff --git a/flights-domain/flights-information/src/api/routes/flights.py b/flights-domain/flights-information/src/api/routes/flights.py index 842283b..ddaf0ad 100644 --- a/flights-domain/flights-information/src/api/routes/flights.py +++ b/flights-domain/flights-information/src/api/routes/flights.py @@ -1,7 +1,15 @@ from typing import Annotated, Optional from asyncreq import request -from fastapi import APIRouter, BackgroundTasks, Depends, Header, HTTPException, Request +from fastapi import ( + APIRouter, + BackgroundTasks, + Depends, + Header, + HTTPException, + Request, + Response, +) from sqlalchemy.orm import Session from src.api.config import API_MESSAGES @@ -67,10 +75,12 @@ async def update_flight( @router.get("", response_model=list[Flight]) def get_flights( + response: Response, origin: Optional[str] = None, destination: Optional[str] = None, lastUpdated: Optional[str] = None, future: Optional[str] = None, + page: Optional[int] = 1, db: Session = Depends(get_db), ): if origin and lastUpdated: @@ -86,7 +96,8 @@ def get_flights( db=db, destination=destination, future=future ) else: - flights = flight_crud.get_flights(db=db) + flights, count = flight_crud.get_flights(db=db, page=page) + response.headers["X-Count"] = str(count) if not flights: raise HTTPException(status_code=404, detail="Flights not found") diff --git a/gateway/src/api/log.py b/gateway/src/api/log.py deleted file mode 100644 index 00fad6a..0000000 --- a/gateway/src/api/log.py +++ /dev/null @@ -1,150 +0,0 @@ -import json -import logging -import sys -import time -from typing import Callable -from uuid import uuid4 - -from fastapi import FastAPI, Request, Response -from starlette.middleware.base import BaseHTTPMiddleware -from starlette.types import Message - -logging_config = { - "version": 1, - "formatters": { - "json": { - "class": "pythonjsonlogger.jsonlogger.JsonFormatter", - "format": "%(asctime)s %(process)s %(levelname)s", - } - }, - "handlers": { - "console": { - "level": "DEBUG", - "class": "logging.StreamHandler", - "formatter": "json", - "stream": sys.stderr, - } - }, - "root": {"level": "DEBUG", "handlers": ["console"], "propagate": True}, -} - - -class RouterLoggingMiddleware(BaseHTTPMiddleware): - def __init__( - self, app: FastAPI, *, logger: logging.Logger, api_debug: bool = False - ) -> None: - self._logger = logger - self.api_debug = api_debug - super().__init__(app) - - async def dispatch(self, request: Request, call_next: Callable) -> Response: - request_header = request.headers.get("x-api-request-id") - if request_header is not None: - request_id = request_header - else: - request_id: str = str(uuid4()) - - logging_dict = {"X-API-REQUEST-ID": request_id} - - if self.api_debug: - await self.set_body(request) - - response, response_dict = await self._log_response( - call_next, request, request_id - ) - request_dict = await self._log_request(request) - logging_dict["request"] = request_dict - logging_dict["response"] = response_dict - - self._logger.info(logging_dict) - - return response - - async def set_body(self, request: Request): - _receive = await request._receive() - - async def receive() -> Message: - return _receive - - request._receive = receive - - async def _log_request(self, request: Request) -> str: - path = request.url.path - if request.query_params: - path += f"?{request.query_params}" - - request_logging = { - "method": request.method, - "path": path, - "ip": request.client.host, - } - - if self.api_debug: - try: - body = await request.json() - request_logging["body"] = body - except ValueError: - body = None - - return request_logging - - async def _log_response( - self, call_next: Callable, request: Request, request_id: str - ) -> Response: - start_time = time.perf_counter() - response = await self._execute_request(call_next, request, request_id) - finish_time = time.perf_counter() - - overall_status = "successful" if response.status_code < 400 else "failed" - execution_time = finish_time - start_time - - response_logging = { - "status": overall_status, - "status_code": response.status_code, - "time_taken": f"{execution_time:0.4f}s", - } - - if self.api_debug: - resp_body = [ - section async for section in response.__dict__["body_iterator"] - ] - response.__setattr__("body_iterator", AsyncIteratorWrapper(resp_body)) - - try: - resp_body = json.loads(resp_body[0].decode()) - except ValueError: - resp_body = str(resp_body) - - response_logging["body"] = resp_body - - return response, response_logging - - async def _execute_request( - self, call_next: Callable, request: Request, request_id: str - ) -> Response: - try: - request.state.request_id = request_id - response: Response = await call_next(request) - - response.headers["X-API-Request-ID"] = request_id - return response - - except Exception as e: - self._logger.exception( - {"path": request.url.path, "method": request.method, "reason": e} - ) - - -class AsyncIteratorWrapper: - def __init__(self, obj): - self._it = iter(obj) - - def __aiter__(self): - return self - - async def __anext__(self): - try: - value = next(self._it) - except StopIteration: - raise StopAsyncIteration - return value diff --git a/gateway/src/api/main.py b/gateway/src/api/main.py index a2cab61..771e61c 100644 --- a/gateway/src/api/main.py +++ b/gateway/src/api/main.py @@ -7,9 +7,6 @@ from logmiddleware import RouterLoggingMiddleware, logging_config from src.api.config import API_DEBUG from src.api.routes import auth, flights, health, notifications, subscriptions, users -# from src.api.log import RouterLoggingMiddleware, logging_config - - logging.config.dictConfig(logging_config) logger = logging.getLogger(__name__) @@ -33,5 +30,6 @@ app.add_middleware( allow_credentials=True, allow_methods=["POST", "GET", "PUT", "DELETE", "OPTIONS"], allow_headers=["*"], + expose_headers=["x-count"], ) app.add_middleware(RouterLoggingMiddleware, logger=logger, api_debug=API_DEBUG) diff --git a/gateway/src/api/routes/flights.py b/gateway/src/api/routes/flights.py index 65d02d5..56b6f60 100644 --- a/gateway/src/api/routes/flights.py +++ b/gateway/src/api/routes/flights.py @@ -1,7 +1,7 @@ from typing import Annotated, Optional from asyncreq import request -from fastapi import APIRouter, Header, HTTPException, Request +from fastapi import APIRouter, Header, HTTPException, Request, Response from src.api.config import API_FLIGHTS from src.api.routes.auth import checkAuth @@ -29,9 +29,9 @@ async def create_flight( req: Request, authorization: Annotated[str | None, Header()] = None, ): - auth = await checkAuth(req, authorization, isAirline=True) + id = await checkAuth(req, authorization, isAirline=True) flight_data = flight.model_dump() - flight_data["user_id"] = auth["id"] + flight_data["user_id"] = id request_id = req.state.request_id header = {"x-api-request-id": request_id} (response, status, _) = await request( @@ -49,9 +49,9 @@ async def update_flight( req: Request, authorization: Annotated[str | None, Header()] = None, ): - auth = await checkAuth(req, authorization, isAirline=True) + id = await checkAuth(req, authorization, isAirline=True) update = flight_update.model_dump() - update["user_id"] = auth["id"] + update["user_id"] = id request_id = req.state.request_id header = {"x-api-request-id": request_id} (response, status, _) = await request( @@ -65,9 +65,11 @@ async def update_flight( @router.get("", response_model=list[Flight]) async def get_flights( req: Request, + res: Response, origin: Optional[str] = None, destination: Optional[str] = None, lastUpdated: Optional[str] = None, + page: Optional[int] = 1, future: Optional[str] = None, ): query = {} @@ -79,11 +81,14 @@ async def get_flights( query["lastUpdated"] = lastUpdated if future: query["future"] = future + if page: + query["page"] = page request_id = req.state.request_id header = {"x-api-request-id": request_id} - (response, status, _) = await request( + (response, status, headers) = await request( f"{API_FLIGHTS}", "GET", query=query, headers=header ) if status < 200 or status > 204: raise HTTPException(status_code=status, detail=response) + res.headers["x-count"] = headers["x-count"] return response