16 Commits

Author SHA1 Message Date
eead35f822 📝 Add Docs section to the TODO list 2024-01-08 23:03:59 +01:00
d7bef20ffc 📄 Rewrite reference of the origin of the project name 2024-01-07 11:42:44 +01:00
c2c8c81759 📄 Replace WIP tag with a reference to the origin of the project name 2024-01-07 11:39:04 +01:00
98dbf5dfe2 📄 Add WIP tag to the README file 2024-01-07 11:35:46 +01:00
4236f33cb9 📝 Add technical stack description to the README 2024-01-07 11:19:14 +01:00
60ce90b633 🚧 Add C4 Container diagram 2024-01-06 17:45:58 +01:00
c52fc69560 🚧 Add plantuml block to the README to test gitea rendering 2024-01-06 13:32:17 +01:00
e9a13c662e 📝 Change the presentation video chroma subsampling
Cf. https://bugzilla.mozilla.org/show_bug.cgi?id=1368063
2023-12-22 11:36:01 +01:00
93625f12b1 📝 Add thumbnail to the presentation video 2023-12-22 11:27:30 +01:00
b7ed3f83b8 📝 Add main readme file
Signed-off-by: Adrien <me@adrien.run>
2023-12-22 11:03:29 +01:00
f1a47c9621 📝 First try to embed a video in the readme page 2023-12-22 10:59:44 +01:00
7843309f0a 📝 Add a video showing what carrramba-encore-rate service is 2023-12-22 10:48:22 +01:00
cebc9077c3 👽️ Add of the StopAreaStopAssociationFields pdeid and pdeversion fields
These fields have been added by IDFM in its relations resource.
For now, these fields are not used.
2023-10-22 23:41:27 +02:00
f862e124a6 Merge branch 'k8s-integration' into develop 2023-10-22 23:37:44 +02:00
1bb75b28eb ♻️ Use of relative imports for api modules 2023-10-22 23:34:58 +02:00
0a7337a313 ♻️ Put api_server and db_updater scripts on the backend root 2023-10-22 23:31:35 +02:00
23 changed files with 172 additions and 26 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
medias/presentation.png filter=lfs diff=lfs merge=lfs -text
medias/presentation.mp4 filter=lfs diff=lfs merge=lfs -text

124
README.md Normal file
View File

@@ -0,0 +1,124 @@
# 🚧 Carrramba! Encore raté! 🚧
Resident of the Ile-de-France,
- Tired of missing your bus/train/metro ?
- Tired of having to walk to know where your bus/train/metro will stop ?
- Are you looking to display its next passages in the same way as Ile De France Mobilité ?
**Visit [carrramba.adrien.run](https://carrramba.adrien.run/)**
[![Presentation](medias/presentation.png)](medias/presentation.mp4)
# Technical stack
The following figure list the building blocks (docker images) and their interactions to provide the service:
```plantuml
@startuml
!include <C4/C4_Container>
!define ICONURL https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/v2.4.0
!includeurl ICONURL/common.puml
!includeurl ICONURL/font-awesome-5/users.puml
!includeurl ICONURL/devicons2/redis.puml
!includeurl ICONURL/devicons2/sqlalchemy.puml
!includeurl ICONURL/devicons2/postgresql.puml
title Carrramba-encore-rate - Back-end
LAYOUT_WITH_LEGEND()
Person(user, "User", "The service user", $sprite="users")
System_Boundary(cb1, "carrramba-encore-rate") {
Container(app, "SPA", "SolidJS", "The graphical interface used by users to consume services provided")
Container(api, "carrramba-encore-rate-api", "FastAPI / Pydantic 2 / SQLAlchemy 2", "Provides the functionalities serving API endpoints", $sprite="sqlalchemy")
ContainerDb(db, "Postgres", "PostgreSQL database", "Stores stops/stop areas/lines/shapes and associations information.", $sprite="postgresql")
ContainerDb(cache, "In-Memory cache", "Redis", "Store previously computed results (stop, line, destination, next passages, shapes).", $sprite="redis")
Container(db_updater, "db-updater", "Sync the service static data with the IDFM ones")
}
System_Boundary(idfm, "IDFM") {
Container_Ext(static_, "Static data")
Container_Ext(dynamic_, "Dynamic data")
}
Rel(user, app, "Uses")
Rel(app, api, "Uses", "JSON/HTTPS")
Rel_R(api, db, "Reads from", "sync, PSQL")
Rel_L(api, cache, "Reads from and writes to", "sync, REdis Serialization Protocol")
Rel(api, dynamic_, "Get next passages", "JSON/HTTPS")
Rel_L(db_updater, db, "Writes to", "sync, PSQL")
Rel(db_updater, static_, "Get stops, lines andshapes")
@enduml
```
## Back-end
Conventional but efficient:
- [FastAPI](https://fastapi.tiangolo.com/): _FastAPI is a modern, fast (high-performance), web framework for building
APIs with Python 3.8+ based on standard Python type hints._
- [Pydantic 2](https://docs.pydantic.dev/latest/): _Pydantic is the most widely used data validation library for
Python._
- [Sqlalchemy 2](https://www.sqlalchemy.org/): _SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that
gives application developers the full power and flexibility of SQL._
The [Msgspec](https://github.com/jcrist/msgspec) library is used to serialize JSON objects collected from IDFM API
endpoints, this library being faster than Pydantic.
## Front-end
The component is developed using the [SolidJS](https://www.solidjs.com/) library. It brings the following interesting specificities:
- Share with React the same programmatic structures and support for component.
- Fine-grained reactivity architecture: no virtual DOM used to update the components, the browser DOM is directly
updated by SolidJS.
The front-end tries to be as close as possible to the design defined by the IDFM for the displays deployed by the
transport operators in Ile-de-France. These specifications are public and available here:
[PRIM-IDFM](https://prim.iledefrance-mobilites.fr/en/chartes-et-prescriptions).
# TODO
## Features
- [ ] Integration with [Matrix.org](https://matrix.org/ecosystem/integrations/) ecosystem: make the app able to send message to a room when a bus/train/metro will
arriving in X minutes.
- [ ] Add the capability for the users to pin his/her favorite stops.
- [ ] Add the address to the stop location.
## Docs
- [ ] Describe how to build the front-end and back-end docker images.
- [ ] Describe how to deploy them using `docker compose`.
- [ ] Add back-end API description ([openAPI](https://www.openapis.org/)) + generate documentation
([mkdoc](https://www.mkdocs.org/), [Redoc](https://github.com/Redocly/redoc)). The best would be to build and
deploy it (as docker images) using CI/CD... need to test [Agola](https://agola.io/), [Jaypore
CI](https://www.jayporeci.in/) (pipelines configured in Python),
[Woodpecker](https://woodpecker-ci.org/docs/intro) or [Drone](https://www.drone.io/) before.
## Front-end
- [ ] Add unit tests.
- [ ] Make the StopNameInput component liquid.
- [ ] Liquid to responsive Design.
## Back-end
- [ ] Add unit tests.
- [ ] Use [alembic](https://alembic.sqlalchemy.org/en/latest/) to manage the future updates of the database schemas.
- [ ] Add the capability to reload the application configuration on configuration file update (e.g.: credential update by the vault).
- [ ] Rework how the database is updated with the IDFM data: For now the database is cleaned before refilling. It could
be useful to avoid to empty the database and only apply deltas (update/add/remove rows).
- [ ] Could be nice to compare FastAPI with [Litestar](https://litestar.dev/).
<!-- LocalWords: specificities
-->

View File

@@ -30,5 +30,7 @@ env VIRTUAL_ENV=/app/.venv \
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
COPY api /app/api
COPY config.sample.yaml .
COPY api_server.py .
CMD ["python", "./api/main.py"]
CMD ["./api_server.py"]

View File

@@ -35,6 +35,7 @@ env VIRTUAL_ENV=/app/.venv \
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
COPY api /app/api
COPY db_updater /app/db_updater
COPY config.sample.yaml .
COPY db_updater.py .
CMD ["python", "-m", "db_updater.fill_db"]
CMD ["./db_updater.py"]

View File

@@ -0,0 +1,9 @@
# Architecture
The following schema shows the components used to satisfy the `api` service:
![API](./docs/medias/hubble-ui_api.png)

View File

@@ -14,7 +14,7 @@ from sqlalchemy.ext.asyncio import (
)
from .base_class import Base
from settings import DatabaseSettings
from ..settings import DatabaseSettings
logger = getLogger(__name__)

View File

@@ -4,9 +4,9 @@ from fastapi_cache.backends.redis import RedisBackend
from redis import asyncio as aioredis
from yaml import safe_load
from db import db
from idfm_interface.idfm_interface import IdfmInterface
from settings import CacheSettings, Settings
from .db import db
from .idfm_interface.idfm_interface import IdfmInterface
from .settings import CacheSettings, Settings
CONFIG_PATH = environ.get("CONFIG_PATH", "./config.sample.yaml")

View File

@@ -8,8 +8,8 @@ from msgspec import ValidationError
from msgspec.json import Decoder
from .idfm_types import Destinations as IdfmDestinations, IdfmResponse, IdfmState
from db import Database
from models import Line, Stop, StopArea
from ..db import Database
from ..models import Line, Stop, StopArea
class IdfmInterface:

View File

@@ -141,6 +141,8 @@ class StopAreaStopAssociationFields(Struct, kw_only=True):
artid: str | None = None
arrversion: str
zdcid: str
pdeid: str | None = None
pdeversion: int | None = None
version: int
zdaid: str
zdaversion: str

View File

@@ -14,8 +14,8 @@ from sqlalchemy import (
from sqlalchemy.orm import Mapped, mapped_column, relationship, selectinload
from sqlalchemy.sql.expression import tuple_
from db import Base, db
from idfm_interface.idfm_types import (
from ..db import Base, db
from ..idfm_interface.idfm_types import (
IdfmState,
IdfmLineState,
TransportMode,

View File

@@ -26,8 +26,8 @@ from sqlalchemy.orm import (
from sqlalchemy.schema import Index
from sqlalchemy_utils.types.ts_vector import TSVectorType
from db import Base, db
from idfm_interface.idfm_types import TransportMode, IdfmState, StopAreaType
from ..db import Base, db
from ..idfm_interface.idfm_types import TransportMode, IdfmState, StopAreaType
if TYPE_CHECKING:
from .line import Line

View File

@@ -1,7 +1,7 @@
from sqlalchemy import BigInteger, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from db import Base, db
from ..db import Base, db
from .stop import _Stop

View File

@@ -1,8 +1,8 @@
from fastapi import APIRouter, HTTPException
from fastapi_cache.decorator import cache
from models import Line
from schemas import Line as LineSchema, TransportMode
from ..models import Line
from ..schemas import Line as LineSchema, TransportMode
router = APIRouter(prefix="/line", tags=["line"])

View File

@@ -5,16 +5,16 @@ from typing import Sequence
from fastapi import APIRouter, HTTPException
from fastapi_cache.decorator import cache
from idfm_interface import Destinations as IdfmDestinations, TrainStatus
from models import Stop, StopArea, StopShape
from schemas import (
from ..idfm_interface import Destinations as IdfmDestinations, TrainStatus
from ..models import Stop, StopArea, StopShape
from ..schemas import (
NextPassage as NextPassageSchema,
NextPassages as NextPassagesSchema,
Stop as StopSchema,
StopArea as StopAreaSchema,
StopShape as StopShapeSchema,
)
from dependencies import idfm_interface
from ..dependencies import idfm_interface
router = APIRouter(prefix="/stop", tags=["stop"])

View File

@@ -2,7 +2,7 @@ from enum import StrEnum
from pydantic import BaseModel
from idfm_interface import (
from ..idfm_interface import (
IdfmLineState,
IdfmState,
TransportMode as IdfmTransportMode,

View File

@@ -1,6 +1,6 @@
from pydantic import BaseModel
from idfm_interface.idfm_types import TrainStatus
from ..idfm_interface.idfm_types import TrainStatus
class NextPassage(BaseModel):

View File

@@ -1,6 +1,6 @@
from pydantic import BaseModel
from idfm_interface import StopAreaType
from ..idfm_interface import StopAreaType
class Stop(BaseModel):

View File

@@ -12,9 +12,9 @@ from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from db import db
from dependencies import idfm_interface, redis_backend, settings
from routers import line, stop
from api.db import db
from api.dependencies import idfm_interface, redis_backend, settings
from api.routers import line, stop
@asynccontextmanager

BIN
medias/presentation.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
medias/presentation.png (Stored with Git LFS) Normal file

Binary file not shown.