diff --git a/backend/bloom/__init__.py b/backend/bloom/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/bloom/config.py b/backend/bloom/config.py index a89a4462..38372e7b 100644 --- a/backend/bloom/config.py +++ b/backend/bloom/config.py @@ -2,20 +2,13 @@ from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict -from typing import Any from pydantic import ( - AliasChoices, - AmqpDsn, - BaseModel, Field, - ImportString, - PostgresDsn, - RedisDsn, - field_validator, model_validator ) + class Settings(BaseSettings): model_config = SettingsConfigDict( # validate_assignment=True allows to update db_url value as soon as one of @@ -27,44 +20,44 @@ class Settings(BaseSettings): env_ignore_empty=True, env_nested_delimiter='__', env_file='.env', - env_file_encoding = 'utf-8', + env_file_encoding='utf-8', extra='ignore' - ) - - # Déclaration des attributs/paramètres disponibles au sein de la class settings - postgres_user:str = Field(default='') - postgres_password:str = Field(default='') - postgres_hostname:str = Field(min_length=1, - default='localhost') - postgres_port:int = Field(gt=1024, - default=5432) + ) - postgres_db:str = Field(min_length=1,max_length=32,pattern=r'^(?:[a-zA-Z]|_)[\w\d_]*$') + # Déclaration des attributs/paramètres disponibles au sein de la class settings + postgres_user: str = Field(default='') + postgres_password: str = Field(default='') + postgres_hostname: str = Field(min_length=1, + default='localhost') + postgres_port: int = Field(gt=1024, + default=5432) + + postgres_db: str = Field(min_length=1, max_length=32, pattern=r'^(?:[a-zA-Z]|_)[\w\d_]*$') srid: int = Field(default=4326) - spire_token:str = Field(default='') - data_folder:str=Field(default=str(Path(__file__).parent.parent.parent.joinpath('./data'))) - db_url:str=Field(default='') + spire_token: str = Field(default='') + data_folder: str = Field(default=str(Path(__file__).parent.parent.parent.joinpath('./data'))) + db_url: str = Field(default='') redis_host: str = Field(default='localhost') redis_port: int = Field(default=6379) redis_cache_expiration: int = Field(default=900) - - logging_level:str=Field( - default="INFO", - pattern=r'NOTSET|DEBUG|INFO|WARNING|ERROR|CRITICAL' - ) + + logging_level: str = Field( + default="INFO", + pattern=r'NOTSET|DEBUG|INFO|WARNING|ERROR|CRITICAL' + ) @model_validator(mode='after') - def update_db_url(self)->dict: - new_url= f"postgresql://{self.postgres_user}:"\ - f"{self.postgres_password}@{self.postgres_hostname}:"\ - f"{self.postgres_port}/{self.postgres_db}" + def update_db_url(self) -> dict: + new_url = f"postgresql://{self.postgres_user}:" \ + f"{self.postgres_password}@{self.postgres_hostname}:" \ + f"{self.postgres_port}/{self.postgres_db}" if self.db_url != new_url: - self.db_url = new_url + self.db_url = new_url return self settings = Settings(_env_file=os.getenv('BLOOM_CONFIG', - Path(__file__).parent.parent.parent.joinpath('.env')), + Path(__file__).parent.parent.parent.joinpath('.env')), _secrets_dir=os.getenv('BLOOM_SECRETS_DIR', - Path(__file__).parent.parent.parent.joinpath('./secrets'))) + Path(__file__).parent.parent.parent.joinpath('./secrets'))) diff --git a/backend/bloom/container.py b/backend/bloom/container.py index c8c693cd..62ead74f 100644 --- a/backend/bloom/container.py +++ b/backend/bloom/container.py @@ -10,11 +10,12 @@ from bloom.infra.repositories.repository_segment import SegmentRepository from bloom.infra.repositories.repository_zone import ZoneRepository from bloom.services.GetVesselsFromSpire import GetVesselsFromSpire +from bloom.usecase.Excursions import ExcursionUseCase from bloom.usecase.GenerateAlerts import GenerateAlerts from dependency_injector import containers, providers -class UseCases(containers.DeclarativeContainer): +class UseCasesContainer(containers.DeclarativeContainer): config = providers.Configuration() db_url = settings.db_url db = providers.Singleton( @@ -57,14 +58,6 @@ class UseCases(containers.DeclarativeContainer): session_factory=db.provided.session, ) - get_spire_data_usecase = providers.Factory(GetVesselsFromSpire) - - generate_alert_usecase = providers.Factory( - GenerateAlerts, - alert_repository=alert_repository, - raster_repository=raster_repository, - ) - spire_ais_data_repository = providers.Factory( SpireAisDataRepository, session_factory=db.provided.session, @@ -74,3 +67,17 @@ class UseCases(containers.DeclarativeContainer): SegmentRepository, session_factory=db.provided.session, ) + get_spire_data_usecase = providers.Factory( + GetVesselsFromSpire + ) + + generate_alert_usecase = providers.Factory( + GenerateAlerts, + alert_repository=alert_repository, + raster_repository=raster_repository, + ) + + excursion_usecase = providers.Factory( + ExcursionUseCase, + excursion_repository=excursion_repository, + ) diff --git a/backend/bloom/domain/__init__.py b/backend/bloom/domain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/bloom/infra/database/__init__.py b/backend/bloom/infra/database/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/bloom/infra/http/__init__.py b/backend/bloom/infra/http/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/bloom/infra/repositories/__init__.py b/backend/bloom/infra/repositories/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/bloom/infra/repositories/repository_excursion.py b/backend/bloom/infra/repositories/repository_excursion.py index 5f869585..e01eb668 100644 --- a/backend/bloom/infra/repositories/repository_excursion.py +++ b/backend/bloom/infra/repositories/repository_excursion.py @@ -34,18 +34,21 @@ def get_param_from_last_excursion(self, session: Session, vessel_id: int) -> Uni return None return {"arrival_port_id": result.arrival_port_id, "arrival_position": result.arrival_position} - def get_excursions_by_vessel_id(self, session: Session, vessel_id: int) -> List[Excursion]: - """Recheche l'excursion en cours d'un bateau, c'est-à-dire l'excursion qui n'a pas de date d'arrivée""" - stmt = select(sql_model.Excursion).where(sql_model.Excursion.vessel_id == vessel_id) - result = session.execute(stmt).scalars() - if not result: - return [] + def get_excursions_by_vessel_id(self, vessel_id: int) -> List[Excursion]: + with self.session_factory as session: + """Recheche l'excursion en cours d'un bateau, c'est-à-dire l'excursion qui n'a pas de date d'arrivée""" + stmt = select(sql_model.Excursion).where(sql_model.Excursion.vessel_id == vessel_id) + result = session.execute(stmt).scalars() + if not result: + return [] return [ExcursionRepository.map_to_domain(r) for r in result] def get_vessel_excursion_by_id(self, session: Session, vessel_id: int, excursion_id: int) -> Union[Excursion, None]: """Recheche l'excursion en cours d'un bateau, c'est-à-dire l'excursion qui n'a pas de date d'arrivée""" - stmt = select(sql_model.Excursion).where((sql_model.Excursion.vessel_id == vessel_id) - & (sql_model.Excursion.id == excursion_id)) + stmt = select(sql_model.Excursion).where( + (sql_model.Excursion.vessel_id == vessel_id) + & (sql_model.Excursion.id == excursion_id) + ) result = session.execute(stmt).scalar() if not result: return None @@ -164,29 +167,3 @@ def map_to_domain(excursion: sql_model.Excursion) -> Excursion: created_at=excursion.created_at, updated_at=excursion.updated_at ) - - @staticmethod - def map_to_orm(excursion: Excursion) -> sql_model.Excursion: - return sql_model.Excursion( - id=excursion.id, - vessel_id=excursion.vessel_id, - departure_port_id=excursion.departure_port_id, - departure_at=excursion.departure_at, - departure_position=from_shape( - excursion.departure_position) if excursion.departure_position is not None else None, - arrival_port_id=excursion.arrival_port_id, - arrival_at=excursion.arrival_at, - arrival_position=from_shape(excursion.arrival_position) if excursion.arrival_position is not None else None, - excursion_duration=excursion.excursion_duration, - total_time_at_sea=excursion.total_time_at_sea, - total_time_in_amp=excursion.total_time_in_amp, - total_time_in_territorial_waters=excursion.total_time_fishing_in_territorial_waters, - total_time_in_costal_waters=excursion.total_time_fishing_in_costal_waters, - total_time_fishing=excursion.total_time_fishing, - total_time_fishing_in_amp=excursion.total_time_fishing_in_amp, - total_time_fishing_in_territorial_waters=excursion.total_time_fishing_in_territorial_waters, - total_time_fishing_in_costal_waters=excursion.total_time_fishing_in_costal_waters, - total_time_extincting_amp=excursion.total_time_extincting_amp, - created_at=excursion.created_at, - updated_at=excursion.updated_at - ) diff --git a/backend/bloom/infra/repositories/repository_port.py b/backend/bloom/infra/repositories/repository_port.py index 264dc6e1..4783f62c 100644 --- a/backend/bloom/infra/repositories/repository_port.py +++ b/backend/bloom/infra/repositories/repository_port.py @@ -17,17 +17,19 @@ class PortRepository: def __init__(self, session_factory: Callable) -> None: self.session_factory = session_factory - def get_port_by_id(self, session: Session, port_id: int) -> Union[Port, None]: - entity = session.get(sql_model.Port, port_id) + def get_port_by_id(self, port_id: int) -> Union[Port, None]: + with self.session_factory() as session: + entity = session.get(sql_model.Port, port_id) if entity is not None: return PortRepository.map_to_domain(entity) else: return None - def get_all_ports(self, session: Session) -> List[Port]: - q = session.query(sql_model.Port) - if not q: - return [] + def get_all_ports(self) -> List[Port]: + with self.session_factory() as session: + q = session.query(sql_model.Port) + if not q: + return [] return [PortRepository.map_to_domain(entity) for entity in q] def get_empty_geometry_buffer_ports(self, session: Session) -> list[Port]: diff --git a/backend/bloom/infra/repositories/repository_zone.py b/backend/bloom/infra/repositories/repository_zone.py index aa9c2472..b359aa46 100644 --- a/backend/bloom/infra/repositories/repository_zone.py +++ b/backend/bloom/infra/repositories/repository_zone.py @@ -26,6 +26,14 @@ def get_all_zones(self, session: Session) -> list[Zone]: return [] return [ZoneRepository.map_to_domain(entity) for entity in q] + def get_all_zone_categories(self, session: Session) -> list[ZoneCategory]: + q = session.query(sql_model.Zone.category, + sql_model.Zone.sub_category).distinct() + q=session.execute(q) + if not q: + return [] + return [ZoneRepository.map_to_domain(ZoneCategory(category=cat,sub_category=sub)) for cat,sub in q] + def get_all_zone_categories(self, session: Session) -> list[ZoneCategory]: q = session.query(sql_model.Zone.category, sql_model.Zone.sub_category).distinct() @@ -75,6 +83,12 @@ def map_to_domain(zone: sql_model.Zone) -> Zone: json_data=zone.json_data, created_at=zone.created_at, ) + @staticmethod + def map_to_domain(category: ZoneCategory) -> ZoneCategory: + return ZoneCategory( + category=category.category, + sub_category=category.sub_category + ) @staticmethod def map_zonecategory_to_domain(category: ZoneCategory) -> ZoneCategory: diff --git a/backend/bloom/routers/__init__.py b/backend/bloom/routers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/bloom/routers/excursions.py b/backend/bloom/routers/excursions.py new file mode 100644 index 00000000..5e6a4373 --- /dev/null +++ b/backend/bloom/routers/excursions.py @@ -0,0 +1,73 @@ +import json +import time +from typing import List + +import redis +from dependency_injector.wiring import inject, Provide +from fastapi import APIRouter, Depends + +from bloom.config import settings +from bloom.container import UseCasesContainer +from bloom.domain.excursion import Excursion +from bloom.logger import logger +from bloom.usecase.Excursions import ExcursionUseCase + +rd = redis.Redis(host=settings.redis_host, port=settings.redis_port, db=0) + +router = APIRouter() + + +@router.get("/vessels/{vessel_id}/excursions") +@inject +async def list_vessel_excursions( + vessel_id: int, + nocache: bool = False, + excursion_usecase: ExcursionUseCase = Depends( + Provide[UseCasesContainer.excursion_usecase] + ) +) -> List[Excursion]: + endpoint = f"/vessels/{vessel_id}/excursions" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + return excursion_usecase.list_vessel_excursions(vessel_id) + + +@router.get("/vessels/{vessel_id}/excursions/{excursions_id}") +async def get_vessel_excursion( + vessel_id: int, + excursions_id: int, + excursion_usecase: ExcursionUseCase = Depends( + Provide[UseCasesContainer.excursion_usecase] + )): + return excursion_usecase.get_excursion_by_id(vessel_id, excursions_id) + + +@router.get("/vessels/{vessel_id}/excursions/{excursions_id}/segments") +@inject +async def list_vessel_excursion_segments( + vessel_id: int, + excursions_id: int, + excursion_usecase: ExcursionUseCase = Depends( + Provide[UseCasesContainer.excursion_usecase] + ) +): + return excursion_usecase.get_excursions_segments(vessel_id, excursions_id) + + +@router.get("/vessels/{vessel_id}/excursions/{excursions_id}/segments/{segment_id}") +@inject +async def get_vessel_excursion_segment( + vessel_id: int, + excursions_id: int, + segment_id: int, + excursion_usecase: ExcursionUseCase = Depends( + Provide[UseCasesContainer.excursion_usecase] + ) +): + return await excursion_usecase.get_segment_by_id(vessel_id, excursions_id, segment_id) \ No newline at end of file diff --git a/backend/bloom/routers/ports.py b/backend/bloom/routers/ports.py new file mode 100644 index 00000000..0aeffddb --- /dev/null +++ b/backend/bloom/routers/ports.py @@ -0,0 +1,44 @@ +import json +import time + +from redis import Redis +from dependency_injector.wiring import inject, Provide +from fastapi import APIRouter, Depends +from bloom.config import settings +from bloom.container import UseCasesContainer +from bloom.logger import logger +from bloom.usecase.Ports import PortUseCase + +router = APIRouter() +redis_client = Redis(host=settings.redis_host, port=settings.redis_port, db=0) + + +@router.get("/ports") +@inject +async def list_ports( + nocache: bool = False, + ports_usecase: PortUseCase = Depends( + Provide[UseCasesContainer.excursion_usecase] + ) +): + endpoint = f"/ports" + cache = redis_client.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + return ports_usecase.list_ports() + + +@router.get("/ports/{port_id}") +@inject +async def get_port( + port_id: int, + ports_usecase: PortUseCase = Depends( + Provide[UseCasesContainer.excursion_usecase] + ) +): + return ports_usecase.get_port_by_id(port_id) diff --git a/backend/bloom/routers/vessels.py b/backend/bloom/routers/vessels.py new file mode 100644 index 00000000..2f2560c1 --- /dev/null +++ b/backend/bloom/routers/vessels.py @@ -0,0 +1,91 @@ +from fastapi import APIRouter + +from redis import Redis +import json +import time +from bloom.config import settings +from bloom.container import UseCasesContainer +from bloom.logger import logger + +rd = Redis(host=settings.redis_host, port=settings.redis_port, db=0) + + +router = APIRouter() + + +@router.get("/vessels") +async def list_vessels(nocache: bool = False): + endpoint = f"/vessels" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCasesContainer() + vessel_repository = use_cases.vessel_repository() + db = use_cases.db() + with db.session() as session: + + json_data = [json.loads(v.model_dump_json() if v else "{}") + for v in vessel_repository.get_vessels_list(session)] + await rd.set(endpoint, json.dumps(json_data)) + await rd.expire(endpoint, settings.redis_cache_expiration) + return json_data + + +@router.get("/vessels/{vessel_id}") +async def get_vessel(vessel_id: int): + use_cases = UseCasesContainer() + vessel_repository = use_cases.vessel_repository() + db = use_cases.db() + with db.session() as session: + return vessel_repository.get_vessel_by_id(session, vessel_id) + + +@router.get("/vessels/all/positions/last") +async def list_all_vessel_last_position(nocache: bool = False): + endpoint = f"/vessels/all/positions/last" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCasesContainer() + segment_repository = use_cases.segment_repository() + db = use_cases.db() + with db.session() as session: + json_data = [json.loads(p.model_dump_json() if p else "{}") + for p in segment_repository.get_all_vessels_last_position(session)] + await rd.set(endpoint, json.dumps(json_data)) + await rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return json_data + + +@router.get("/vessels/{vessel_id}/positions/last") +async def get_vessel_last_position(vessel_id: int, nocache: bool = False): + endpoint = f"/vessels/{vessel_id}/positions/last" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCasesContainer() + segment_repository = use_cases.segment_repository() + db = use_cases.db() + with db.session() as session: + result = segment_repository.get_vessel_last_position(session, vessel_id) + json_data = json.loads(result.model_dump_json() if result else "{}") + await rd.set(endpoint, json.dumps(json_data)) + await rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return json_data diff --git a/backend/bloom/routers/zones.py b/backend/bloom/routers/zones.py new file mode 100644 index 00000000..198f98b2 --- /dev/null +++ b/backend/bloom/routers/zones.py @@ -0,0 +1,118 @@ +import json +import time + +import redis +from fastapi import APIRouter +from starlette.requests import Request + +from bloom.config import settings +from bloom.container import UseCasesContainer +from bloom.logger import logger + +rd = redis.Redis(host=settings.redis_host, port=settings.redis_port, db=0) + +router = APIRouter() + + +@router.get("/zones") +async def list_zones(request: Request, nocache: bool = False): + endpoint = f"/zones" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCasesContainer() + zone_repository = use_cases.zone_repository() + db = use_cases.db() + with db.session() as session: + json_data = [json.loads(z.model_dump_json() if z else "{}") + for z in zone_repository.get_all_zones(session)] + await rd.set(endpoint, json.dumps(json_data)) + await rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return json_data + + +@router.get("/zones/all/categories") +async def list_zone_categories(request: Request, nocache: bool = False): + endpoint = f"/zones/all/categories" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCasesContainer() + zone_repository = use_cases.zone_repository() + db = use_cases.db() + with db.session() as session: + json_data = [json.loads(z.model_dump_json() if z else "{}") + for z in zone_repository.get_all_zone_categories(session)] + await rd.set(endpoint, json.dumps(json_data)) + await rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return json_data + + +@router.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): + endpoint = f"/zones/by-category/{category}/by-sub-category/{sub}" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCasesContainer() + zone_repository = use_cases.zone_repository() + db = use_cases.db() + with db.session() as session: + json_data = [json.loads(z.model_dump_json() if z else "{}") + for z in + zone_repository.get_all_zones_by_category(session, category if category != 'all' else None, + sub)] + await rd.set(endpoint, json.dumps(json_data)) + await rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return json_data + + +@router.get("/zones/by-category/{category}") +async def get_zone_all_by_category(category: str = "all", nocache: bool = False): + endpoint = f"/zones/by-category/{category}" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCasesContainer() + zone_repository = use_cases.zone_repository() + db = use_cases.db() + with db.session() as session: + json_data = [json.loads(z.model_dump_json() if z else "{}") + for z in + zone_repository.get_all_zones_by_category(session, category if category != 'all' else None)] + await rd.set(endpoint, json.dumps(json_data)) + await rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return json_data + + +@router.get("/zones/{zones_id}") +async def get_zone(zones_id: int): + use_cases = UseCasesContainer() + zone_repository = use_cases.zone_repository() + db = use_cases.db() + with db.session() as session: + return zone_repository.get_zone_by_id(session, zones_id) diff --git a/backend/bloom/services/GetVesselsFromSpire.py b/backend/bloom/services/GetVesselsFromSpire.py index a4d659a8..12c39d65 100644 --- a/backend/bloom/services/GetVesselsFromSpire.py +++ b/backend/bloom/services/GetVesselsFromSpire.py @@ -14,7 +14,7 @@ def __init__(self) -> None: spire_token = settings.spire_token self.transport = RequestsHTTPTransport( - url="https://api.spire.com/graphql", + url="https://api.sml.kpler.com/graphql", headers={"Authorization": "Bearer " + spire_token}, verify=True, retries=3, @@ -22,6 +22,7 @@ def __init__(self) -> None: ) def create_client(self) -> Client: + logger.info(f"Connecting to {self.transport.url}...") try: client = Client(transport=self.transport, fetch_schema_from_transport=True) except exceptions.ConnectTimeout: diff --git a/backend/bloom/services/api.py b/backend/bloom/services/api.py index 5e0e72a3..8e98d09f 100644 --- a/backend/bloom/services/api.py +++ b/backend/bloom/services/api.py @@ -5,61 +5,62 @@ import json from bloom.config import settings from bloom.container import UseCases -from bloom.domain.vessel import Vessel from bloom.logger import logger rd = redis.Redis(host=settings.redis_host, port=settings.redis_port, db=0) -from datetime import datetime import time - app = FastAPI() + @app.get("/cache/all/flush") -async def cache_all_flush(request:Request): +async def cache_all_flush(request: Request): rd.flushall() - return {"code":0} + return {"code": 0} + @app.get("/vessels") -async def list_vessels(nocache:bool=False): - endpoint=f"/vessels" - cache= rd.get(endpoint) +async def list_vessels(nocache: bool = False): + endpoint = f"/vessels" + cache = rd.get(endpoint) start = time.time() if cache and not nocache: logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") - payload=json.loads(cache) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return payload else: use_cases = UseCases() vessel_repository = use_cases.vessel_repository() db = use_cases.db() with db.session() as session: - + json_data = [json.loads(v.model_dump_json() if v else "{}") - for v in vessel_repository.get_vessels_list(session)] + for v in vessel_repository.get_vessels_list(session)] rd.set(endpoint, json.dumps(json_data)) - rd.expire(endpoint,settings.redis_cache_expiration) + rd.expire(endpoint, settings.redis_cache_expiration) return json_data + @app.get("/vessels/{vessel_id}") async def get_vessel(vessel_id: int): use_cases = UseCases() vessel_repository = use_cases.vessel_repository() db = use_cases.db() with db.session() as session: - return vessel_repository.get_vessel_by_id(session,vessel_id) + 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): - endpoint=f"/vessels/all/positions/last" - cache= rd.get(endpoint) +async def list_all_vessel_last_position(nocache: bool = False): + endpoint = f"/vessels/all/positions/last" + cache = rd.get(endpoint) start = time.time() if cache and not nocache: logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") - payload=json.loads(cache) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return payload else: use_cases = UseCases() @@ -69,41 +70,43 @@ async def list_all_vessel_last_position(nocache:bool=False): json_data = [json.loads(p.model_dump_json() if p else "{}") for p in segment_repository.get_all_vessels_last_position(session)] rd.set(endpoint, json.dumps(json_data)) - rd.expire(endpoint,settings.redis_cache_expiration) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return json_data + @app.get("/vessels/{vessel_id}/positions/last") -async def get_vessel_last_position(vessel_id: int, nocache:bool=False): - endpoint=f"/vessels/{vessel_id}/positions/last" - cache= rd.get(endpoint) +async def get_vessel_last_position(vessel_id: int, nocache: bool = False): + endpoint = f"/vessels/{vessel_id}/positions/last" + cache = rd.get(endpoint) start = time.time() if cache and not nocache: logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") - payload=json.loads(cache) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return payload else: use_cases = UseCases() segment_repository = use_cases.segment_repository() db = use_cases.db() with db.session() as session: - result=segment_repository.get_vessel_last_position(session,vessel_id) + result = segment_repository.get_vessel_last_position(session, vessel_id) json_data = json.loads(result.model_dump_json() if result else "{}") rd.set(endpoint, json.dumps(json_data)) - rd.expire(endpoint,settings.redis_cache_expiration) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return json_data + @app.get("/vessels/{vessel_id}/excursions") -async def list_vessel_excursions(vessel_id: int, nocache:bool=False): - endpoint=f"/vessels/{vessel_id}/excursions" - cache= rd.get(endpoint) +async def list_vessel_excursions(vessel_id: int, nocache: bool = False): + endpoint = f"/vessels/{vessel_id}/excursions" + cache = rd.get(endpoint) start = time.time() if cache and not nocache: logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") - payload=json.loads(cache) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return payload else: use_cases = UseCases() @@ -111,37 +114,39 @@ async def list_vessel_excursions(vessel_id: int, nocache:bool=False): db = use_cases.db() with db.session() as session: json_data = [json.loads(p.model_dump_json() if p else "{}") - for p in excursion_repository.get_excursions_by_vessel_id(session,vessel_id)] + for p in excursion_repository.get_excursions_by_vessel_id(session, vessel_id)] rd.set(endpoint, json.dumps(json_data)) - rd.expire(endpoint,settings.redis_cache_expiration) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return json_data @app.get("/vessels/{vessel_id}/excursions/{excursions_id}") -async def get_vessel_excursion(vessel_id: int,excursions_id: int): +async def get_vessel_excursion(vessel_id: int, excursions_id: int): use_cases = UseCases() excursion_repository = use_cases.excursion_repository() db = use_cases.db() with db.session() as session: - return excursion_repository.get_vessel_excursion_by_id(session,vessel_id,excursions_id) + return excursion_repository.get_vessel_excursion_by_id(session, vessel_id, excursions_id) @app.get("/vessels/{vessel_id}/excursions/{excursions_id}/segments") -async def list_vessel_excursion_segments(vessel_id: int,excursions_id: int): +async def list_vessel_excursion_segments(vessel_id: int, excursions_id: int): use_cases = UseCases() segment_repository = use_cases.segment_repository() db = use_cases.db() with db.session() as session: - return segment_repository.list_vessel_excursion_segments(session,vessel_id,excursions_id) + 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): +async def get_vessel_excursion_segment(vessel_id: int, excursions_id: int, segment_id: int): use_cases = UseCases() segment_repository = use_cases.segment_repository() db = use_cases.db() with db.session() as session: - return segment_repository.get_vessel_excursion_segment_by_id(session,vessel_id,excursions_id,segment_id) + 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): @@ -164,25 +169,27 @@ async def list_ports(request:Request,nocache:bool=False): rd.expire(endpoint,settings.redis_cache_expiration) logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") return json_data - + + @app.get("/ports/{port_id}") -async def get_port(port_id:int): +async def get_port(port_id: int): use_cases = UseCases() port_repository = use_cases.port_repository() db = use_cases.db() with db.session() as session: - return port_repository.get_port_by_id(session,port_id) + return port_repository.get_port_by_id(session, port_id) + @app.get("/zones") -async def list_zones(request:Request,nocache:bool=False): - endpoint=f"/zones" - cache= rd.get(endpoint) +async def list_zones(request: Request, nocache: bool = False): + endpoint = f"/zones" + cache = rd.get(endpoint) start = time.time() if cache and not nocache: logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") - payload=json.loads(cache) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return payload else: use_cases = UseCases() @@ -192,34 +199,84 @@ async def list_zones(request:Request,nocache:bool=False): json_data = [json.loads(z.model_dump_json() if z else "{}") for z in zone_repository.get_all_zones(session)] rd.set(endpoint, json.dumps(json_data)) - rd.expire(endpoint,settings.redis_cache_expiration) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return json_data + @app.get("/zones/all/categories") -async def list_zone_categories(request:Request,nocache:bool=False): - endpoint=f"/zones/all/categories" - cache= rd.get(endpoint) +async def list_zone_categories(request: Request, nocache: bool = False): + endpoint = f"/zones/all/categories" + cache = rd.get(endpoint) start = time.time() if cache and not nocache: logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") - payload=json.loads(cache) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") return payload else: use_cases = UseCases() zone_repository = use_cases.zone_repository() db = use_cases.db() with db.session() as session: - json_data = [json.loads(z.model_dump_json() if z else "{}") + json_data = [json.loads(z.model_dump_json() if z else "{}") for z in zone_repository.get_all_zone_categories(session)] rd.set(endpoint, json.dumps(json_data)) - rd.expire(endpoint,settings.redis_cache_expiration) - logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") + rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + 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): + endpoint = f"/zones/by-category/{category}/by-sub-category/{sub}" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCases() + zone_repository = use_cases.zone_repository() + db = use_cases.db() + with db.session() as session: + json_data = [json.loads(z.model_dump_json() if z else "{}") + for z in + zone_repository.get_all_zones_by_category(session, category if category != 'all' else None, + sub)] + rd.set(endpoint, json.dumps(json_data)) + rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return json_data + + +@app.get("/zones/by-category/{category}") +async def get_zone_all_by_category(category: str = "all", nocache: bool = False): + endpoint = f"/zones/by-category/{category}" + cache = rd.get(endpoint) + start = time.time() + if cache and not nocache: + logger.debug(f"{endpoint} cached ({settings.redis_cache_expiration})s") + payload = json.loads(cache) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") + return payload + else: + use_cases = UseCases() + zone_repository = use_cases.zone_repository() + db = use_cases.db() + with db.session() as session: + json_data = [json.loads(z.model_dump_json() if z else "{}") + for z in + zone_repository.get_all_zones_by_category(session, category if category != 'all' else None)] + rd.set(endpoint, json.dumps(json_data)) + rd.expire(endpoint, settings.redis_cache_expiration) + logger.debug(f"{endpoint} elapsed Time: {time.time() - start}") 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): +async def get_zone_all_by_category(category:str,sub:str=None,nocache:bool=False): endpoint=f"/zones/by-category/{category}/by-sub-category/{sub}" cache= rd.get(endpoint) start = time.time() @@ -241,7 +298,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): +async def get_zone_all_by_category(category:str="amp",nocache:bool=0): endpoint=f"/zones/by-category/{category}" cache= rd.get(endpoint) start = time.time() @@ -255,26 +312,27 @@ async def get_zone_all_by_category(category:str="all",nocache:bool=False): zone_repository = use_cases.zone_repository() db = use_cases.db() with db.session() as session: - json_data = [json.loads(z.model_dump_json() if z else "{}") - for z in zone_repository.get_all_zones_by_category(session,category if category != 'all' else None)] + json_data = [z.model_dump_json() + for z in zone_repository.get_all_zones_by_category(session,category)] rd.set(endpoint, json.dumps(json_data)) rd.expire(endpoint,settings.redis_cache_expiration) logger.debug(f"{endpoint} elapsed Time: {time.time()-start}") return json_data - + @app.get("/zones/{zones_id}") -async def get_zone(zones_id:int): +async def get_zone(zones_id: int): use_cases = UseCases() zone_repository = use_cases.zone_repository() db = use_cases.db() with db.session() as session: - return zone_repository.get_zone_by_id(session,zones_id) + return zone_repository.get_zone_by_id(session, zones_id) + @app.get("/") -async def root(request:Request): +async def root(request: Request): return { - "cache_all_flush": f"{request.url_for('cache_all_flush')}", - "ports": f"{request.url_for('list_ports')}", - "vessels": f"{request.url_for('list_vessels')}", - "zones": f"{request.url_for('list_zones')}", - } \ No newline at end of file + "cache_all_flush": f"{request.url_for('cache_all_flush')}", + "ports": f"{request.url_for('list_ports')}", + "vessels": f"{request.url_for('list_vessels')}", + "zones": f"{request.url_for('list_zones')}", + } diff --git a/backend/bloom/services/geo.py b/backend/bloom/services/geo.py index 81f3d674..588aa69b 100644 --- a/backend/bloom/services/geo.py +++ b/backend/bloom/services/geo.py @@ -2,7 +2,7 @@ import pandas as pd import geopandas as gpd -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.config import settings def find_positions_in_port_buffer(vessel_positions: List[tuple]) -> List[tuple]: @@ -28,7 +28,7 @@ def find_positions_in_port_buffer(vessel_positions: List[tuple]) -> List[tuple]: ) # Get all ports from DataBase - use_cases = UseCases() + use_cases = UseCasesContainer() port_repository = use_cases.port_repository() db = use_cases.db() with db.session() as session: diff --git a/backend/bloom/tasks/__init__.py b/backend/bloom/tasks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/bloom/tasks/clean_positions.py b/backend/bloom/tasks/clean_positions.py index fc6e26cc..c9d1448e 100644 --- a/backend/bloom/tasks/clean_positions.py +++ b/backend/bloom/tasks/clean_positions.py @@ -8,7 +8,7 @@ from geopy import distance from shapely.geometry import Point -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.vessel_position import VesselPosition from bloom.infra.repositories.repository_task_execution import TaskExecutionRepository from bloom.logger import logger @@ -51,7 +51,7 @@ def to_coords(row: pd.Series) -> pd.Series: def run(batch_time): - use_cases = UseCases() + use_cases = UseCasesContainer() db = use_cases.db() spire_repository = use_cases.spire_ais_data_repository() excursion_repository = use_cases.excursion_repository() diff --git a/backend/bloom/tasks/compute_port_geometry_buffer.py b/backend/bloom/tasks/compute_port_geometry_buffer.py index 186caa86..fb1cd858 100644 --- a/backend/bloom/tasks/compute_port_geometry_buffer.py +++ b/backend/bloom/tasks/compute_port_geometry_buffer.py @@ -5,7 +5,7 @@ import pyproj import shapely from bloom.config import settings -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.logger import logger from scipy.spatial import Voronoi from shapely.geometry import LineString, Polygon @@ -93,7 +93,7 @@ def assign_voronoi_buffer(ports: gpd.GeoDataFrame) -> gpd.GeoDataFrame: def run() -> None: - use_cases = UseCases() + use_cases = UseCasesContainer() port_repository = use_cases.port_repository() db = use_cases.db() items = [] diff --git a/backend/bloom/tasks/convert_spire_vessels_to_spire_ais_data.py b/backend/bloom/tasks/convert_spire_vessels_to_spire_ais_data.py index df120b9e..c1bcaea0 100644 --- a/backend/bloom/tasks/convert_spire_vessels_to_spire_ais_data.py +++ b/backend/bloom/tasks/convert_spire_vessels_to_spire_ais_data.py @@ -1,7 +1,7 @@ from time import perf_counter from typing import Generator -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.spire_ais_data import SpireAisData from bloom.infra.database.sql_model import VesselPositionSpire from bloom.logger import logger @@ -9,7 +9,7 @@ from shapely import Point from sqlalchemy.orm.session import Session -use_cases = UseCases() +use_cases = UseCasesContainer() vessel_repo = use_cases.vessel_repository() spire_ais_data_repo = use_cases.spire_ais_data_repository() db = use_cases.db() diff --git a/backend/bloom/tasks/create_update_excursions_segments.py b/backend/bloom/tasks/create_update_excursions_segments.py index a9fa2f22..2fbcea40 100644 --- a/backend/bloom/tasks/create_update_excursions_segments.py +++ b/backend/bloom/tasks/create_update_excursions_segments.py @@ -10,7 +10,7 @@ from shapely.geometry import Point from sqlalchemy.orm import Session -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.excursion import Excursion from bloom.domain.segment import Segment from bloom.infra.repositories.repository_task_execution import TaskExecutionRepository @@ -38,7 +38,7 @@ def to_coords(row: pd.Series) -> pd.Series: def add_excursion(session: Session, vessel_id: int, departure_at: datetime, departure_position: Optional[Point] = None) -> int: - use_cases = UseCases() + use_cases = UseCasesContainer() excursion_repository = use_cases.excursion_repository() result = excursion_repository.get_param_from_last_excursion(session, vessel_id) @@ -74,7 +74,7 @@ def add_excursion(session: Session, vessel_id: int, departure_at: datetime, def close_excursion(session: Session, excursion_id: int, port_id: int, latitude: float, longitude: float, arrived_at: datetime) -> None: - use_cases = UseCases() + use_cases = UseCasesContainer() excursion_repository = use_cases.excursion_repository() excursion = excursion_repository.get_excursion_by_id(session, excursion_id) @@ -87,7 +87,7 @@ def close_excursion(session: Session, excursion_id: int, port_id: int, latitude: def run(): - use_cases = UseCases() + use_cases = UseCasesContainer() db = use_cases.db() segment_repository = use_cases.segment_repository() vessel_position_repository = use_cases.vessel_position_repository() diff --git a/backend/bloom/tasks/load_dim_port_from_csv.py b/backend/bloom/tasks/load_dim_port_from_csv.py index 00a2eef8..ad275304 100644 --- a/backend/bloom/tasks/load_dim_port_from_csv.py +++ b/backend/bloom/tasks/load_dim_port_from_csv.py @@ -5,7 +5,7 @@ import pandas as pd import pycountry from bloom.config import settings -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.port import Port from bloom.infra.database.errors import DBException from bloom.logger import logger @@ -29,7 +29,7 @@ def map_to_domain(row) -> Port: def run(csv_file_name: str) -> None: - use_cases = UseCases() + use_cases = UseCasesContainer() port_repository = use_cases.port_repository() db = use_cases.db() diff --git a/backend/bloom/tasks/load_dim_vessel_from_csv.py b/backend/bloom/tasks/load_dim_vessel_from_csv.py index 1d0a1e96..0dde3873 100644 --- a/backend/bloom/tasks/load_dim_vessel_from_csv.py +++ b/backend/bloom/tasks/load_dim_vessel_from_csv.py @@ -3,7 +3,7 @@ import pandas as pd from bloom.config import settings -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.vessel import Vessel from bloom.infra.database.errors import DBException from bloom.logger import logger @@ -33,7 +33,7 @@ def map_to_domain(row: pd.Series) -> Vessel: def run(csv_file_name: str) -> None: - use_cases = UseCases() + use_cases = UseCasesContainer() vessel_repository = use_cases.vessel_repository() db = use_cases.db() diff --git a/backend/bloom/tasks/load_dim_zone_amp_from_csv.py b/backend/bloom/tasks/load_dim_zone_amp_from_csv.py index 99e3ae1a..b2ed85ad 100644 --- a/backend/bloom/tasks/load_dim_zone_amp_from_csv.py +++ b/backend/bloom/tasks/load_dim_zone_amp_from_csv.py @@ -5,7 +5,7 @@ from shapely import wkb from bloom.config import settings -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.zone import Zone from bloom.logger import logger @@ -34,8 +34,8 @@ def map_to_domain(row: pd.Series) -> Zone: ) -def run(): - use_cases = UseCases() +def run(csv_file_name: str): + use_cases = UseCasesContainer() db = use_cases.db() zone_repository = use_cases.zone_repository() diff --git a/backend/bloom/tasks/load_fct_excursions_from_csv.py b/backend/bloom/tasks/load_fct_excursions_from_csv.py index 208deb13..c96e1d5b 100644 --- a/backend/bloom/tasks/load_fct_excursions_from_csv.py +++ b/backend/bloom/tasks/load_fct_excursions_from_csv.py @@ -6,7 +6,7 @@ from datetime import datetime from shapely.geometry import Point from bloom.config import settings -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.infra.database.errors import DBException from bloom.logger import logger from bloom.domain.spire_ais_data import SpireAisData @@ -119,7 +119,7 @@ def get_point(end_position: str) -> Point: return Point(end_position[1], end_position[0]) def run(excursion_csv_filename: str, segment_csv_filename: str, spire_csv_filename: str) -> None: - use_cases = UseCases() + use_cases = UseCasesContainer() excursion_repository = use_cases.excursion_repository() # vessel_position_repository = use_cases.vessel_position_repository() segment_repository = use_cases.segment_repository() diff --git a/backend/bloom/tasks/load_spire_data_from_csv.py b/backend/bloom/tasks/load_spire_data_from_csv.py index 3968b1f1..e02bcd70 100644 --- a/backend/bloom/tasks/load_spire_data_from_csv.py +++ b/backend/bloom/tasks/load_spire_data_from_csv.py @@ -3,7 +3,7 @@ import pandas as pd from bloom.config import settings -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.spire_ais_data import SpireAisData from bloom.infra.database.errors import DBException from bloom.logger import logger @@ -51,7 +51,7 @@ def map_to_domain(row: pd.Series) -> SpireAisData: def run(csv_file_name: str): - use_cases = UseCases() + use_cases = UseCasesContainer() db = use_cases.db() spire_ais_data_repository = use_cases.spire_ais_data_repository() diff --git a/backend/bloom/tasks/load_spire_data_from_json.py b/backend/bloom/tasks/load_spire_data_from_json.py index 04fbf907..36dc4512 100644 --- a/backend/bloom/tasks/load_spire_data_from_json.py +++ b/backend/bloom/tasks/load_spire_data_from_json.py @@ -3,14 +3,14 @@ from pathlib import Path from time import perf_counter -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.infra.http.spire_api_utils import map_raw_vessels_to_domain from bloom.logger import logger from pydantic import ValidationError def run(file_name: str) -> None: - use_cases = UseCases() + use_cases = UseCasesContainer() spire_ais_data_repository = use_cases.spire_ais_data_repository() db = use_cases.db() diff --git a/backend/bloom/tasks/update_vessel_data_voyage.py b/backend/bloom/tasks/update_vessel_data_voyage.py index ef4886ff..daff9964 100644 --- a/backend/bloom/tasks/update_vessel_data_voyage.py +++ b/backend/bloom/tasks/update_vessel_data_voyage.py @@ -1,7 +1,7 @@ from datetime import datetime, timezone from time import perf_counter -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.spire_ais_data import SpireAisData from bloom.domain.vessel import Vessel from bloom.domain.vessel_data import VesselData @@ -47,7 +47,7 @@ def map_ais_data_to_vessel_voyage(ais_data: SpireAisData, vessel: Vessel) -> Uni def run() -> None: - use_cases = UseCases() + use_cases = UseCasesContainer() spire_ais_data_repository = use_cases.spire_ais_data_repository() vessel_repository = use_cases.vessel_repository() db = use_cases.db() diff --git a/backend/bloom/usecase/Excursions.py b/backend/bloom/usecase/Excursions.py new file mode 100644 index 00000000..716ee3cf --- /dev/null +++ b/backend/bloom/usecase/Excursions.py @@ -0,0 +1,30 @@ +import json + +from bloom.config import settings +from bloom.logger import logger + + +class ExcursionUseCase: + def __init__(self, excursions_repository, redis_client): + self.excursions_repository = excursions_repository + self.redis_client = redis_client + self.endpoint = f"/vessels/excursions" + + def list_vessel_excursions(self, vessel_id, with_cache=True): + return self.excursions_repository.get_vessel_excursions(vessel_id, with_cache) + + async def get_excursions_by_vessel_id(self, vessel_id): + excursions = self.excursions_repository.get_excursions_by_vessel_id(vessel_id) + + await self.redis_client.set(self.endpoint, json.dumps(excursions)) + await self.redis_client.expire(self.endpoint, settings.redis_cache_expiration) + return self.excursions_repository.get_excursions_by_vessel_id(vessel_id) + + async def get_excursion_by_id(self, vessel_id, excursions_id): + return self.excursions_repository.get_excursion_by_id(vessel_id, excursions_id) + + async def get_excursions_segments(self, vessel_id, excursions_id, segment_id): + return self.excursions_repository.get(vessel_id, excursions_id, segment_id) + + async def get_segment_by_id(self, vessel_id, excursions_id, segment_id): + return self.excursions_repository.get_segment_by_id(vessel_id, excursions_id, segment_id) diff --git a/backend/bloom/usecase/Ports.py b/backend/bloom/usecase/Ports.py new file mode 100644 index 00000000..83d69b11 --- /dev/null +++ b/backend/bloom/usecase/Ports.py @@ -0,0 +1,20 @@ +import json + +from bloom.config import settings +from bloom.infra.repositories.repository_port import PortRepository + + +class PortUseCase: + def __init__(self, ports_repository: PortRepository, redis_client): + self.ports_repository = ports_repository + self.redis_client = redis_client + self.caching_key = 'ports:caching' + + async def list_ports(self): + ports = self.ports_repository.get_all_ports() + await self.redis_client.set(self.caching_key, json.dumps(ports)) + await self.redis_client.expire(self.caching_key, settings.redis_cache_expiration) + return ports + + async def get_port_by_id(self, port_id): + return self.ports_repository.get_port_by_id(port_id) diff --git a/backend/bloom/usecase/__init__.py b/backend/bloom/usecase/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/bloom/tasks/load_spire_data_from_api.py b/backend/load_spire_data_from_api.py similarity index 92% rename from backend/bloom/tasks/load_spire_data_from_api.py rename to backend/load_spire_data_from_api.py index 29d9b1e5..2337ca8d 100644 --- a/backend/bloom/tasks/load_spire_data_from_api.py +++ b/backend/load_spire_data_from_api.py @@ -4,7 +4,7 @@ from pathlib import Path from time import perf_counter -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.vessel import Vessel from bloom.infra.http.spire_api_utils import map_raw_vessels_to_domain from bloom.logger import logger @@ -12,7 +12,7 @@ def run(dump_path: str) -> None: - use_cases = UseCases() + use_cases = UseCasesContainer() spire_ais_data_repository = use_cases.spire_ais_data_repository() spire_traffic_usecase = use_cases.get_spire_data_usecase() vessel_repository = use_cases.vessel_repository() @@ -58,8 +58,8 @@ def run(dump_path: str) -> None: ) args = parser.parse_args() time_start = perf_counter() - logger.info("DEBUT - Chargement des données JSON depuis l'API SPIRE") + logger.info("DEBUT - Chargement des données JSON depuis l'API Kpler") run(args.dump_path) time_end = perf_counter() duration = time_end - time_start - logger.info(f"FIN - Chargement des données depuis l'API SPIRE en {duration:.2f}s") + logger.info(f"FIN - Chargement des données depuis l'API Kpler en {duration:.2f}s") diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 00000000..92c3ea65 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,64 @@ +from fastapi import FastAPI +from starlette.requests import Request + +from bloom.container import UseCasesContainer +from bloom.routers import excursions, zones, vessels, ports +from bloom.routers.vessels import rd + + +def init_db(container): + db = container.db() + db.create_database() + + +def create_app() -> FastAPI: + container = init_container() + + init_db(container) + server = init_server(container) + # server.add_exception_handler(DBException, db_exception_handler) + # server.add_exception_handler(ValidationError, validation_exception_handler) + # server.add_exception_handler(Exception, generic_exception_handler) + + return server + + +def schedule_crawling(): + pass + + +def init_container(): + container = UseCasesContainer() + container.wire( + modules=[ + zones, + vessels, + excursions, + ports + ] + ) + return container + + +def init_server(container): + server = FastAPI(dependencies=[]) + server.container = container + server.include_router(excursions.router) + server.include_router(ports.router) + server.include_router(vessels.router) + server.include_router(zones.router) + return server + + +app = create_app() + + +@app.get("/") +async def root(request: Request): + return {"status": "ok"} + + +@app.get("/cache/all/flush") +async def cache_all_flush(request: Request): + await rd.flushall() + return {"code": 0} diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 00000000..2961024d --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,329 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --output-file=requirements.txt pyproject.toml +# +alembic==1.13.2 + # via bloom (pyproject.toml) +annotated-types==0.7.0 + # via pydantic +anyio==4.4.0 + # via + # gql + # httpx + # starlette + # watchfiles +async-timeout==4.0.3 + # via redis +attrs==23.2.0 + # via fiona +autopep8==2.0.4 + # via bloom (pyproject.toml) +backoff==2.2.1 + # via gql +build==1.2.1 + # via poetry +cachecontrol[filecache]==0.14.0 + # via poetry +certifi==2024.7.4 + # via + # fiona + # httpcore + # httpx + # pyproj + # requests +cffi==1.16.0 + # via xattr +charset-normalizer==3.3.2 + # via requests +cleo==2.1.0 + # via poetry +click==8.1.7 + # via + # click-plugins + # cligj + # fiona + # typer + # uvicorn +click-plugins==1.1.1 + # via fiona +cligj==0.7.2 + # via fiona +contourpy==1.2.1 + # via matplotlib +crashtest==0.4.1 + # via + # cleo + # poetry +cycler==0.12.1 + # via matplotlib +dependency-injection==1.2.0 + # via bloom (pyproject.toml) +dependency-injector==4.41.0 + # via bloom (pyproject.toml) +distlib==0.3.8 + # via virtualenv +dnspython==2.6.1 + # via email-validator +dulwich==0.21.7 + # via poetry +email-validator==2.2.0 + # via fastapi +et-xmlfile==1.1.0 + # via openpyxl +exceptiongroup==1.2.1 + # via anyio +fastapi==0.111.0 + # via bloom (pyproject.toml) +fastapi-cli==0.0.4 + # via fastapi +fastjsonschema==2.20.0 + # via poetry +filelock==3.15.4 + # via + # cachecontrol + # virtualenv +fiona==1.9.6 + # via geopandas +fonttools==4.53.1 + # via matplotlib +geoalchemy2==0.14.7 + # via bloom (pyproject.toml) +geographiclib==2.0 + # via geopy +geopandas==0.14.4 + # via bloom (pyproject.toml) +geopy==2.4.1 + # via bloom (pyproject.toml) +gql==3.5.0 + # via bloom (pyproject.toml) +graphql-core==3.2.3 + # via gql +h11==0.14.0 + # via + # httpcore + # uvicorn +httpcore==1.0.5 + # via httpx +httptools==0.6.1 + # via uvicorn +httpx==0.27.0 + # via fastapi +idna==3.7 + # via + # anyio + # email-validator + # httpx + # requests + # yarl +importlib-metadata==8.0.0 + # via keyring +installer==0.7.0 + # via poetry +jaraco-classes==3.4.0 + # via keyring +jinja2==3.1.4 + # via fastapi +keyring==24.3.1 + # via poetry +kiwisolver==1.4.5 + # via matplotlib +mako==1.3.5 + # via alembic +markdown-it-py==3.0.0 + # via rich +markupsafe==2.1.5 + # via + # jinja2 + # mako +matplotlib==3.8.4 + # via bloom (pyproject.toml) +mdurl==0.1.2 + # via markdown-it-py +more-itertools==10.3.0 + # via jaraco-classes +msgpack==1.0.8 + # via cachecontrol +multidict==6.0.5 + # via yarl +numpy==1.26.4 + # via + # contourpy + # geopandas + # matplotlib + # pandas + # scipy + # shapely +openpyxl==3.1.5 + # via bloom (pyproject.toml) +orjson==3.10.6 + # via fastapi +packaging==24.1 + # via + # build + # geoalchemy2 + # geopandas + # matplotlib + # poetry +pandas==2.2.2 + # via + # bloom (pyproject.toml) + # geopandas +pexpect==4.9.0 + # via poetry +pillow==10.4.0 + # via matplotlib +pkginfo==1.11.1 + # via poetry +platformdirs==4.2.2 + # via + # poetry + # virtualenv +poetry==1.8.3 + # via + # bloom (pyproject.toml) + # poetry-plugin-export +poetry-core==1.9.0 + # via + # poetry + # poetry-plugin-export +poetry-plugin-export==1.8.0 + # via poetry +psycopg2-binary==2.9.9 + # via bloom (pyproject.toml) +ptyprocess==0.7.0 + # via pexpect +pycodestyle==2.12.0 + # via autopep8 +pycountry==23.12.11 + # via bloom (pyproject.toml) +pycparser==2.22 + # via cffi +pydantic==2.6.4 + # via + # bloom (pyproject.toml) + # fastapi + # pydantic-settings +pydantic-core==2.16.3 + # via pydantic +pydantic-settings==2.2.1 + # via bloom (pyproject.toml) +pygments==2.18.0 + # via rich +pyparsing==3.1.2 + # via matplotlib +pyproj==3.6.1 + # via geopandas +pyproject-hooks==1.1.0 + # via + # build + # poetry +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +python-dotenv==1.0.1 + # via + # bloom (pyproject.toml) + # pydantic-settings + # uvicorn +python-multipart==0.0.9 + # via fastapi +pytz==2024.1 + # via pandas +pyyaml==6.0.1 + # via + # bloom (pyproject.toml) + # uvicorn +rapidfuzz==3.9.4 + # via cleo +redis==5.0.7 + # via bloom (pyproject.toml) +requests==2.31.0 + # via + # bloom (pyproject.toml) + # cachecontrol + # poetry + # requests-toolbelt +requests-toolbelt==1.0.0 + # via + # bloom (pyproject.toml) + # poetry +rich==13.7.1 + # via typer +scipy==1.12.0 + # via bloom (pyproject.toml) +shapely==2.0.4 + # via + # bloom (pyproject.toml) + # geopandas +shellingham==1.5.4 + # via + # poetry + # typer +six==1.16.0 + # via + # dependency-injector + # fiona + # python-dateutil +slack-sdk==3.27.2 + # via bloom (pyproject.toml) +sniffio==1.3.1 + # via + # anyio + # httpx +sqlalchemy==2.0.31 + # via + # alembic + # bloom (pyproject.toml) + # geoalchemy2 +starlette==0.37.2 + # via fastapi +tomli==2.0.1 + # via + # autopep8 + # build + # poetry +tomlkit==0.12.5 + # via poetry +trove-classifiers==2024.7.2 + # via poetry +typer==0.12.3 + # via fastapi-cli +typing-extensions==4.12.2 + # via + # alembic + # anyio + # fastapi + # pydantic + # pydantic-core + # sqlalchemy + # typer + # uvicorn +tzdata==2024.1 + # via pandas +ujson==5.10.0 + # via fastapi +urllib3==2.2.2 + # via + # dulwich + # requests +uvicorn[standard]==0.30.1 + # via + # bloom (pyproject.toml) + # fastapi +uvloop==0.19.0 + # via uvicorn +virtualenv==20.26.3 + # via poetry +watchfiles==0.22.0 + # via uvicorn +websockets==12.0 + # via uvicorn +xattr==1.1.0 + # via poetry +yarl==1.9.4 + # via gql +zipp==3.19.2 + # via importlib-metadata diff --git a/backend/tests/test_alert.py b/backend/tests/test_alert.py index e8a15a56..b1fe7929 100644 --- a/backend/tests/test_alert.py +++ b/backend/tests/test_alert.py @@ -1,4 +1,4 @@ -from bloom.container import UseCases +from bloom.container import UseCasesContainer from bloom.domain.alert import Alert from datetime import datetime, timezone @@ -13,7 +13,7 @@ def test_launch_alert(): - use_cases = UseCases() + use_cases = UseCasesContainer() alert_usecase = use_cases.generate_alert_usecase() status_code = alert_usecase.send_slack_alert( test_alert, diff --git a/clevercloud/cron.json b/clevercloud/cron.json new file mode 100644 index 00000000..c8b3b95b --- /dev/null +++ b/clevercloud/cron.json @@ -0,0 +1,3 @@ +[ + "*/15 * * * * /home/bas/venv/bin/python /home/bas/app_7244f095-70bc-43c3-9950-d075c01af05f/backend/load_spire_data_from_api.py" +] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index e0db921c..c80250b6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -46,7 +46,7 @@ services: environment: - REDIS_PASSWORD=${REDIS_PASSWORD:-redis} - REDIS_PORT=${REDIS_PORT:-6379} - volumes: + volumes: - bloom-redis:/data networks: - bloom_net @@ -63,6 +63,8 @@ services: - bloom-data:/var/lib/postgresql/data networks: - bloom_net + volumes: + - bloom-data:/var/lib/postgresql/data healthcheck: # PostGis database initialization is done with two steps (postgres+postgis) # This causes healthcheck to be valid before real full initialization @@ -96,7 +98,7 @@ services: dockerfile: ./docker/frontend/dev.Dockerfile args: APP_DIR: /app - + volumes: #- ./frontend:/app #- ./frontend/node_modules:/app/node_modules