Skip to content

Commit

Permalink
début refacto + datetimerange + auth
Browse files Browse the repository at this point in the history
  • Loading branch information
RV committed Oct 7, 2024
1 parent 3c2be7c commit d381cd5
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 52 deletions.
59 changes: 59 additions & 0 deletions backend/bloom/domain/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from fastapi import Request
from pydantic import BaseModel, ConfigDict, Field,conint
from typing import Generic,TypeVar, List
from typing_extensions import Annotated, Literal, Optional
from datetime import datetime, timedelta
from enum import Enum
from pydantic.generics import GenericModel
from fastapi.security import APIKeyHeader

## Reference for pagination design
## https://jayhawk24.hashnode.dev/how-to-implement-pagination-in-fastapi-feat-sqlalchemy
X_API_KEY_HEADER=APIKeyHeader(name="x-key")

class DatetimeRangeRequest(BaseModel):
start_at: datetime = datetime.now()-timedelta(days=7)
end_at: datetime = datetime.now()

class OrderByEnum(str, Enum):
ascending = "ASC"
descending = "DESC"

class OrderByRequest(BaseModel):
order: OrderByEnum = OrderByEnum.ascending

class PaginatedRequest(BaseModel):
offset: int|None = 0
limit: int|None = 100
order_by: OrderByRequest = OrderByEnum.ascending


class PageParams(BaseModel):
""" Request query params for paginated API. """
offset: conint(ge=0) = 0
limit: conint(ge=1, le=1000) = 100

T = TypeVar("T")

class PagedResponseSchema(GenericModel,Generic[T]):
total: int
limit: int
offset: int
next: str|None
previous: str|None
results: List[T]

def paginate(request: Request, page_params: PageParams, query, ResponseSchema: BaseModel) -> PagedResponseSchema[T]:
"""Paginate the query."""

print(f"{request.url.scheme}://{request.client}/{request.url.path}")
paginated_query = query.offset((page_params.offset) * page_params.limit).limit(page_params.limit).all()

return PagedResponseSchema(
total=query.count(),
offset=page_params.offset,
limit=page_params.limit,
next="",
previous="",
results=[ResponseSchema.from_orm(item) for item in paginated_query],
)
10 changes: 4 additions & 6 deletions backend/bloom/domain/metrics.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from pydantic import BaseModel, ConfigDict
from typing import Generic,TypeVar, List
from typing_extensions import Annotated, Literal, Optional
from datetime import datetime, timedelta
from enum import Enum

class ResponseMetricsVesselInActiviySchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
mmsi: int
ship_name: str
Expand Down Expand Up @@ -38,9 +41,4 @@ class ResponseMetricsZoneVisitingTimeByVesselSchema(BaseModel):
vessel_name: str
vessel_type: Optional[str] = None
vessel_length_class: Optional[str] = None
zone_visiting_time_by_vessel: timedelta


class TemporalRequest(BaseModel):
start_at: datetime
end_at: datetime = datetime.now()
zone_visiting_time_by_vessel: timedelta
57 changes: 29 additions & 28 deletions backend/bloom/routers/metrics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, Body,Request
from redis import Redis
from bloom.config import settings
from bloom.container import UseCases
Expand All @@ -9,11 +9,13 @@
from bloom.infra.database import sql_model
from bloom.infra.repositories.repository_segment import SegmentRepository
import json
from pydantic import BaseModel, ConfigDict
from bloom.domain.metrics import ResponseMetricsVesselInActiviySchema,\
ResponseMetricsZoneVisitedSchema,\
ResponseMetricsZoneVisitingTimeByVesselSchema,\
DatetimeRangeRequest
from bloom.domain.metrics import (ResponseMetricsVesselInActiviySchema,
ResponseMetricsZoneVisitedSchema,
ResponseMetricsZoneVisitingTimeByVesselSchema)
from bloom.domain.api import ( DatetimeRangeRequest,
PaginatedRequest,OrderByRequest,OrderByEnum,
paginate,PagedResponseSchema,PageParams,
X_API_KEY_HEADER)

router = APIRouter()
redis_client = Redis(host=settings.redis_host, port=settings.redis_port, db=0)
Expand All @@ -24,10 +26,11 @@
@router.get("/metrics/vessels-in-activity",
response_model=list[ResponseMetricsVesselInActiviySchema],
tags=['metrics'])
def read_metrics_vessels_in_activity_total(start_at: datetime,
end_at: datetime = datetime.now(),
limit: int = None,
order_by: str = 'DESC'
def read_metrics_vessels_in_activity_total(request: Request,
datetime_range: DatetimeRangeRequest = Depends(),
#pagination: PageParams = Depends(),
order: OrderByRequest = Depends(),
auth: str = Depends(X_API_KEY_HEADER),
):
use_cases = UseCases()
db = use_cases.db()
Expand Down Expand Up @@ -56,25 +59,24 @@ def read_metrics_vessels_in_activity_total(start_at: datetime,
.join(sql_model.Vessel, sql_model.Excursion.vessel_id == sql_model.Vessel.id)\
.where(
or_(
sql_model.Excursion.arrival_at.between(start_at,end_at),
and_(sql_model.Excursion.departure_at <= end_at,
sql_model.Excursion.arrival_at.between(datetime_range.start_at,datetime_range.end_at),
and_(sql_model.Excursion.departure_at <= datetime_range.end_at,
sql_model.Excursion.arrival_at == None))
)\
.group_by(sql_model.Vessel.id,sql_model.Excursion.total_time_at_sea)
stmt = stmt.limit(limit) if limit != None else stmt
#stmt = stmt.limit(pagination.limit) if pagination.limit != None else stmt

stmt = stmt.order_by(sql_model.Excursion.total_time_at_sea.asc())\
if order_by.upper() == 'ASC' \
if order.order == OrderByEnum.ascending \
else stmt.order_by(sql_model.Excursion.total_time_at_sea.desc())
return session.execute(stmt).all()

@router.get("/metrics/zone-visited",
response_model=list[ResponseMetricsZoneVisitedSchema],
tags=['metrics'] )
def read_metrics_vessels_in_activity_total(start_at: datetime,
end_at: datetime = datetime.now(),
limit: int = None,
order_by: str = 'DESC'):
def read_metrics_vessels_in_activity_total(datetime_range: DatetimeRangeRequest = Depends(),
pagination: PaginatedRequest = Depends(),
auth: str = Depends(X_API_KEY_HEADER),):
use_cases = UseCases()
db = use_cases.db()
with db.session() as session:
Expand All @@ -87,25 +89,25 @@ def read_metrics_vessels_in_activity_total(start_at: datetime,
)\
.where(
or_(
sql_model.Segment.timestamp_start.between(start_at,end_at),
sql_model.Segment.timestamp_end.between(start_at,end_at),)
sql_model.Segment.timestamp_start.between(datetime_range.start_at,datetime_range.end_at),
sql_model.Segment.timestamp_end.between(datetime_range.start_at,datetime_range.end_at),)
)\
.group_by(sql_model.Zone.id)
stmt = stmt.limit(limit) if limit != None else stmt
stmt = stmt.order_by("visiting_duration")\
if order_by.upper() == 'ASC' \
if pagination.order_by == OrderByRequest.ascending \
else stmt.order_by("visiting_duration")
return session.execute(stmt).all()

@router.get("/metrics/zones/{zone_id}/visiting-time-by-vessel",
response_model=list[ResponseMetricsZoneVisitingTimeByVesselSchema],
tags=['metrics'])
def read_metrics_zone_visiting_time_by_vessel(
datetime_range: Annotated[DatetimeRangeRequest,Body()],
zone_id: int,
start_at: datetime,
end_at: datetime = datetime.now(),
limit: int = None,
order_by: str = 'DESC'):
order_by: str = 'DESC',
auth: str = Depends(X_API_KEY_HEADER),):
use_cases = UseCases()
db = use_cases.db()
with db.session() as session:
Expand Down Expand Up @@ -135,8 +137,7 @@ def read_metrics_zone_visiting_time_by_vessel(
def read_metrics_vessels_visits_by_visit_type(
vessel_id: int,
visit_type: str,
start_at: datetime,
end_at: datetime = None,
limit: int = 10,
orderBy: str = 'DESC'):
datetime_range: DatetimeRangeRequest = Depends(),
pagination: PaginatedRequest = Depends(),
auth: str = Depends(X_API_KEY_HEADER),):
pass
39 changes: 21 additions & 18 deletions backend/bloom/services/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from fastapi.security import APIKeyHeader

from bloom.routers.metrics import router as router_metrics

header_scheme = APIKeyHeader(name="x-key")
from bloom.domain.api import ( DatetimeRangeRequest,
PaginatedRequest,OrderByRequest,
paginate,PagedResponseSchema,PageParams,
X_API_KEY_HEADER)

import redis
import json
Expand All @@ -28,13 +30,14 @@ def check_apikey(key:str):
return True

@app.get("/cache/all/flush")
async def cache_all_flush(request:Request,key: str = Depends(header_scheme)):
async def cache_all_flush(request:Request,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
rd.flushall()
return {"code":0}

@app.get("/vessels")
async def list_vessels(nocache:bool=False,key: str = Depends(header_scheme)):
async def list_vessels(nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
print(f"KEY:{key}")
check_apikey(key)
endpoint=f"/vessels"
cache= rd.get(endpoint)
Expand All @@ -57,7 +60,7 @@ async def list_vessels(nocache:bool=False,key: str = Depends(header_scheme)):
return json_data

@app.get("/vessels/{vessel_id}")
async def get_vessel(vessel_id: int,key: str = Depends(header_scheme)):
async def get_vessel(vessel_id: int,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
use_cases = UseCases()
vessel_repository = use_cases.vessel_repository()
Expand All @@ -66,7 +69,7 @@ async def get_vessel(vessel_id: int,key: str = Depends(header_scheme)):
return vessel_repository.get_vessel_by_id(session,vessel_id)

@app.get("/vessels/all/positions/last")
async def list_all_vessel_last_position(nocache:bool=False,key: str = Depends(header_scheme)):
async def list_all_vessel_last_position(nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
endpoint=f"/vessels/all/positions/last"
cache= rd.get(endpoint)
Expand All @@ -89,7 +92,7 @@ async def list_all_vessel_last_position(nocache:bool=False,key: str = Depends(he
return json_data

@app.get("/vessels/{vessel_id}/positions/last")
async def get_vessel_last_position(vessel_id: int, nocache:bool=False,key: str = Depends(header_scheme)):
async def get_vessel_last_position(vessel_id: int, nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
endpoint=f"/vessels/{vessel_id}/positions/last"
cache= rd.get(endpoint)
Expand All @@ -112,7 +115,7 @@ async def get_vessel_last_position(vessel_id: int, nocache:bool=False,key: str =
return json_data

@app.get("/vessels/{vessel_id}/excursions")
async def list_vessel_excursions(vessel_id: int, nocache:bool=False,key: str = Depends(header_scheme)):
async def list_vessel_excursions(vessel_id: int, nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
endpoint=f"/vessels/{vessel_id}/excursions"
cache= rd.get(endpoint)
Expand All @@ -136,7 +139,7 @@ async def list_vessel_excursions(vessel_id: int, nocache:bool=False,key: str = D


@app.get("/vessels/{vessel_id}/excursions/{excursions_id}")
async def get_vessel_excursion(vessel_id: int,excursions_id: int,key: str = Depends(header_scheme)):
async def get_vessel_excursion(vessel_id: int,excursions_id: int,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
use_cases = UseCases()
excursion_repository = use_cases.excursion_repository()
Expand All @@ -146,7 +149,7 @@ async def get_vessel_excursion(vessel_id: int,excursions_id: int,key: str = Depe


@app.get("/vessels/{vessel_id}/excursions/{excursions_id}/segments")
async def list_vessel_excursion_segments(vessel_id: int,excursions_id: int,key: str = Depends(header_scheme)):
async def list_vessel_excursion_segments(vessel_id: int,excursions_id: int,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
use_cases = UseCases()
segment_repository = use_cases.segment_repository()
Expand All @@ -155,7 +158,7 @@ async def list_vessel_excursion_segments(vessel_id: int,excursions_id: int,key:
return segment_repository.list_vessel_excursion_segments(session,vessel_id,excursions_id)

@app.get("/vessels/{vessel_id}/excursions/{excursions_id}/segments/{segment_id}")
async def get_vessel_excursion_segment(vessel_id: int,excursions_id: int, segment_id:int,key: str = Depends(header_scheme)):
async def get_vessel_excursion_segment(vessel_id: int,excursions_id: int, segment_id:int,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
use_cases = UseCases()
segment_repository = use_cases.segment_repository()
Expand All @@ -164,7 +167,7 @@ async def get_vessel_excursion_segment(vessel_id: int,excursions_id: int, segmen
return segment_repository.get_vessel_excursion_segment_by_id(session,vessel_id,excursions_id,segment_id)

@app.get("/ports")
async def list_ports(request:Request,nocache:bool=False,key: str = Depends(header_scheme)):
async def list_ports(request:Request,nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
endpoint=f"/ports"
cache= rd.get(endpoint)
Expand All @@ -188,7 +191,7 @@ async def list_ports(request:Request,nocache:bool=False,key: str = Depends(heade


@app.get("/ports/{port_id}")
async def get_port(port_id:int,key: str = Depends(header_scheme)):
async def get_port(port_id:int,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
use_cases = UseCases()
port_repository = use_cases.port_repository()
Expand All @@ -197,7 +200,7 @@ async def get_port(port_id:int,key: str = Depends(header_scheme)):
return port_repository.get_port_by_id(session,port_id)

@app.get("/zones")
async def list_zones(request:Request,nocache:bool=False,key: str = Depends(header_scheme)):
async def list_zones(request:Request,nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
endpoint=f"/zones"
cache= rd.get(endpoint)
Expand All @@ -220,7 +223,7 @@ async def list_zones(request:Request,nocache:bool=False,key: str = Depends(heade
return json_data

@app.get("/zones/all/categories")
async def list_zone_categories(request:Request,nocache:bool=False,key: str = Depends(header_scheme)):
async def list_zone_categories(request:Request,nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
endpoint=f"/zones/all/categories"
cache= rd.get(endpoint)
Expand All @@ -243,7 +246,7 @@ async def list_zone_categories(request:Request,nocache:bool=False,key: str = Dep
return json_data

@app.get("/zones/by-category/{category}/by-sub-category/{sub}")
async def get_zone_all_by_category(category:str="all",sub:str=None,nocache:bool=False,key: str = Depends(header_scheme)):
async def get_zone_all_by_category(category:str="all",sub:str=None,nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
endpoint=f"/zones/by-category/{category}/by-sub-category/{sub}"
cache= rd.get(endpoint)
Expand All @@ -266,7 +269,7 @@ async def get_zone_all_by_category(category:str="all",sub:str=None,nocache:bool=
return json_data

@app.get("/zones/by-category/{category}")
async def get_zone_all_by_category(category:str="all",nocache:bool=False,key: str = Depends(header_scheme)):
async def get_zone_all_by_category(category:str="all",nocache:bool=False,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
endpoint=f"/zones/by-category/{category}"
cache= rd.get(endpoint)
Expand All @@ -289,7 +292,7 @@ async def get_zone_all_by_category(category:str="all",nocache:bool=False,key: st
return json_data

@app.get("/zones/{zones_id}")
async def get_zone(zones_id:int,key: str = Depends(header_scheme)):
async def get_zone(zones_id:int,key: str = Depends(X_API_KEY_HEADER)):
check_apikey(key)
use_cases = UseCases()
zone_repository = use_cases.zone_repository()
Expand Down

0 comments on commit d381cd5

Please sign in to comment.