feat: Initialize frontend and backend structure with essential configurations
- Added TypeScript build info for frontend. - Created Vite configuration for React application. - Implemented pre-commit hook to run checks before commits. - Set up PostgreSQL Dockerfile with PostGIS support and initialization scripts. - Added database creation script for PostgreSQL with necessary extensions. - Established Python project configuration with dependencies and development tools. - Developed pre-commit script to enforce code quality checks for backend and frontend. - Created PowerShell script to set up Git hooks path.
This commit is contained in:
11
backend/app/repositories/__init__.py
Normal file
11
backend/app/repositories/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Repository abstractions for database access."""
|
||||
|
||||
from backend.app.repositories.stations import StationRepository
|
||||
from backend.app.repositories.tracks import TrackRepository
|
||||
from backend.app.repositories.trains import TrainRepository
|
||||
|
||||
__all__ = [
|
||||
"StationRepository",
|
||||
"TrackRepository",
|
||||
"TrainRepository",
|
||||
]
|
||||
39
backend/app/repositories/base.py
Normal file
39
backend/app/repositories/base.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from typing import Generic, Iterable, Optional, Sequence, Type, TypeVar
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.app.db.models import Base
|
||||
|
||||
ModelT = TypeVar("ModelT", bound=Base)
|
||||
|
||||
|
||||
class BaseRepository(Generic[ModelT]):
|
||||
"""Provide common CRUD helpers for SQLAlchemy models."""
|
||||
|
||||
model: Type[ModelT]
|
||||
|
||||
def __init__(self, session: Session) -> None:
|
||||
self.session = session
|
||||
|
||||
def get(self, identifier: object) -> Optional[ModelT]:
|
||||
return self.session.get(self.model, identifier)
|
||||
|
||||
def list(self) -> Sequence[ModelT]:
|
||||
statement = sa.select(self.model)
|
||||
return list(self.session.scalars(statement))
|
||||
|
||||
def add(self, instance: ModelT) -> ModelT:
|
||||
self.session.add(instance)
|
||||
return instance
|
||||
|
||||
def add_all(self, instances: Iterable[ModelT]) -> None:
|
||||
self.session.add_all(instances)
|
||||
|
||||
def delete(self, instance: ModelT) -> None:
|
||||
self.session.delete(instance)
|
||||
|
||||
def flush(self) -> None:
|
||||
self.session.flush()
|
||||
37
backend/app/repositories/stations.py
Normal file
37
backend/app/repositories/stations.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from geoalchemy2.elements import WKTElement
|
||||
|
||||
from backend.app.db.models import Station
|
||||
from backend.app.repositories.base import BaseRepository
|
||||
from backend.app.models import StationCreate
|
||||
|
||||
|
||||
class StationRepository(BaseRepository[Station]):
|
||||
model = Station
|
||||
|
||||
def __init__(self, session: Session) -> None:
|
||||
super().__init__(session)
|
||||
|
||||
def list_active(self) -> list[Station]:
|
||||
statement = sa.select(self.model).where(self.model.is_active.is_(True))
|
||||
return list(self.session.scalars(statement))
|
||||
|
||||
@staticmethod
|
||||
def _point(latitude: float, longitude: float) -> WKTElement:
|
||||
return WKTElement(f"POINT({longitude} {latitude})", srid=4326)
|
||||
|
||||
def create(self, data: StationCreate) -> Station:
|
||||
station = Station(
|
||||
name=data.name,
|
||||
osm_id=data.osm_id,
|
||||
code=data.code,
|
||||
location=self._point(data.latitude, data.longitude),
|
||||
elevation_m=data.elevation_m,
|
||||
is_active=data.is_active,
|
||||
)
|
||||
self.session.add(station)
|
||||
return station
|
||||
50
backend/app/repositories/tracks.py
Normal file
50
backend/app/repositories/tracks.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from geoalchemy2.elements import WKTElement
|
||||
from uuid import UUID
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.app.db.models import Track
|
||||
from backend.app.repositories.base import BaseRepository
|
||||
from backend.app.models import TrackCreate
|
||||
|
||||
|
||||
class TrackRepository(BaseRepository[Track]):
|
||||
model = Track
|
||||
|
||||
def __init__(self, session: Session) -> None:
|
||||
super().__init__(session)
|
||||
|
||||
def list_all(self) -> list[Track]:
|
||||
statement = sa.select(self.model)
|
||||
return list(self.session.scalars(statement))
|
||||
|
||||
@staticmethod
|
||||
def _ensure_uuid(value: UUID | str) -> UUID:
|
||||
if isinstance(value, UUID):
|
||||
return value
|
||||
return UUID(str(value))
|
||||
|
||||
@staticmethod
|
||||
def _line_string(coordinates: list[tuple[float, float]]) -> WKTElement:
|
||||
if len(coordinates) < 2:
|
||||
raise ValueError("Track geometry requires at least two coordinate pairs")
|
||||
parts = [f"{lon} {lat}" for lat, lon in coordinates]
|
||||
return WKTElement(f"LINESTRING({', '.join(parts)})", srid=4326)
|
||||
|
||||
def create(self, data: TrackCreate) -> Track:
|
||||
coordinates = list(data.coordinates)
|
||||
geometry = self._line_string(coordinates)
|
||||
track = Track(
|
||||
name=data.name,
|
||||
start_station_id=self._ensure_uuid(data.start_station_id),
|
||||
end_station_id=self._ensure_uuid(data.end_station_id),
|
||||
length_meters=data.length_meters,
|
||||
max_speed_kph=data.max_speed_kph,
|
||||
is_bidirectional=data.is_bidirectional,
|
||||
status=data.status,
|
||||
track_geometry=geometry,
|
||||
)
|
||||
self.session.add(track)
|
||||
return track
|
||||
40
backend/app/repositories/trains.py
Normal file
40
backend/app/repositories/trains.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from uuid import UUID
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.app.db.models import Train
|
||||
from backend.app.repositories.base import BaseRepository
|
||||
from backend.app.models import TrainCreate
|
||||
|
||||
|
||||
class TrainRepository(BaseRepository[Train]):
|
||||
model = Train
|
||||
|
||||
def __init__(self, session: Session) -> None:
|
||||
super().__init__(session)
|
||||
|
||||
def list_all(self) -> list[Train]:
|
||||
statement = sa.select(self.model)
|
||||
return list(self.session.scalars(statement))
|
||||
|
||||
@staticmethod
|
||||
def _optional_uuid(value: UUID | str | None) -> UUID | None:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, UUID):
|
||||
return value
|
||||
return UUID(str(value))
|
||||
|
||||
def create(self, data: TrainCreate) -> Train:
|
||||
train = Train(
|
||||
designation=data.designation,
|
||||
operator_id=self._optional_uuid(data.operator_id),
|
||||
home_station_id=self._optional_uuid(data.home_station_id),
|
||||
capacity=data.capacity,
|
||||
max_speed_kph=data.max_speed_kph,
|
||||
consist=data.consist,
|
||||
)
|
||||
self.session.add(train)
|
||||
return train
|
||||
Reference in New Issue
Block a user