From 40c7877271b534c6e1e9328ade91a546968bf1f0 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Fri, 14 Feb 2025 19:28:56 +0100 Subject: [PATCH 01/29] frontend: pass VITE_BACKEND_BASE_URL via docker --- .env.example | 3 +++ docker-compose.yml | 4 +++- frontend/Dockerfile | 3 +++ frontend/src/components/StationSearch.svelte | 4 +++- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e841cdf --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Specify the backend URL which is used in the frontend. You can not adjust this to the backend container_name as it is not reachable from the frontend! +# Do not add a "/" at the end, as the frontend will add the path to the API endpoint itself. +VITE_BACKEND_BASE_URL=http://localhost:8000 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 5eb74af..f712b67 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,8 @@ services: restart: always ports: [ "3000:3000" ] networks: [ "navigator-network" ] + environment: + - VITE_BACKEND_BASE_URL=${VITE_BACKEND_BASE_URL} navigator-backend: container_name: navigator-backend @@ -28,7 +30,7 @@ services: volumes: - ./redis:/usr/local/etc/redis - navigator-stations:/data - command: ["redis-server", "/usr/local/etc/redis/redis.conf"] + command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ] networks: navigator-network: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 8fcd689..6dd5c0b 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -4,6 +4,9 @@ WORKDIR /app COPY . . RUN npm install + +ENV VITE_BACKEND_BASE_URL=http://localhost:8000 + RUN npm run build RUN npm prune --production # Remove dev dependencies diff --git a/frontend/src/components/StationSearch.svelte b/frontend/src/components/StationSearch.svelte index 3e1da32..8242441 100644 --- a/frontend/src/components/StationSearch.svelte +++ b/frontend/src/components/StationSearch.svelte @@ -18,7 +18,9 @@ const searchStations = async (query: string) => { const searchParams = new URLSearchParams({ query }).toString(); - const response = await fetch(`/api/v1/stations?${searchParams}`, { method: "GET" }); + + const baseUrl = import.meta.env.VITE_BACKEND_BASE_URL; + const response = await fetch(`${baseUrl}/api/v1/stations?${searchParams}`, { method: "GET" }); if (!response.ok) return; const jsonData = await response.json(); From b04ce91ea1b72428bbee33ea061118eead0508e7 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Fri, 14 Feb 2025 19:29:15 +0100 Subject: [PATCH 02/29] backend: standardized redis password to match with users.acl --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f712b67..6c74076 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: REDIS_PORT: 6379 REDIS_DB: 0 REDIS_USER: navigator-stations - REDIS_PASSWORD: pass-your-pw-here + REDIS_PASSWORD: password navigator-redis: container_name: navigator-redis From d95c33d863714d7ecf0a23b1705713769d16c467 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Fri, 14 Feb 2025 19:29:25 +0100 Subject: [PATCH 03/29] add default nginx config --- nginx/nginx.conf | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 nginx/nginx.conf diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..c8a7ae1 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,43 @@ +server { + listen 80; + listen [::]:80; + + server_name example.com; # Adjust your domain here + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name example.com; # Adjust your domain here + + # Place here your SSL keys + ssl_certificate /etc/nginx/certs/.pem; + ssl_certificate_key /etc/nginx/certs/.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + # Backend base URL + location /api/ { + proxy_pass http://navigator-backend:8000; # container_name of the backend + proxy_http_version 1.1; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + # Frontend base URL + location / { + proxy_pass http://navigator-frontend:3000; # container_name of the frontend + proxy_http_version 1.1; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} \ No newline at end of file From 0bb28fb6f4933f8554d581ca197b21ec089e52dc Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Fri, 14 Feb 2025 23:41:48 +0100 Subject: [PATCH 04/29] frontend: made goto async --- frontend/src/components/timetable/TimetableNavbar.svelte | 8 ++++---- frontend/src/components/timetable/TimetableSearch.svelte | 4 ++-- frontend/src/lib/index.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/timetable/TimetableNavbar.svelte b/frontend/src/components/timetable/TimetableNavbar.svelte index 2153c1f..d338cdc 100644 --- a/frontend/src/components/timetable/TimetableNavbar.svelte +++ b/frontend/src/components/timetable/TimetableNavbar.svelte @@ -10,9 +10,9 @@ { + onclick={async () => { type = "departures"; - gotoTimetable( + await gotoTimetable( page.params.evaNumber, type, page.url.searchParams.get("startDate") ?? DateTime.now().set({ second: 0, millisecond: 0 }).toISO() @@ -23,9 +23,9 @@ { + onclick={async () => { type = "arrivals"; - gotoTimetable( + await gotoTimetable( page.params.evaNumber, type, page.url.searchParams.get("startDate") ?? DateTime.now().set({ second: 0, millisecond: 0 }).toISO() diff --git a/frontend/src/components/timetable/TimetableSearch.svelte b/frontend/src/components/timetable/TimetableSearch.svelte index db91a0e..aec8c2d 100644 --- a/frontend/src/components/timetable/TimetableSearch.svelte +++ b/frontend/src/components/timetable/TimetableSearch.svelte @@ -61,9 +61,9 @@ class="{stationSelected && dateSelected ? 'bg-accent text-black' : 'bg-primary text-text hover:bg-secondary'} flex items-center justify-center rounded-3xl px-4 font-bold text-background md:text-2xl" - onclick={() => { + onclick={async () => { if (!stationSelected || !dateSelected) return; - gotoTimetable(stationSelected?.evaNumber, typeSelected, dateSelected.toISO()); + await gotoTimetable(stationSelected?.evaNumber, typeSelected, dateSelected.toISO()); }} > Search diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts index e1f8273..f91283e 100644 --- a/frontend/src/lib/index.ts +++ b/frontend/src/lib/index.ts @@ -2,8 +2,8 @@ import { goto } from "$app/navigation"; import type { Stop } from "$models/station"; -const gotoTimetable = (evaNumber: string, type: "departures" | "arrivals", startDate: string) => { - goto(`/${evaNumber}/${type}?startDate=${encodeURIComponent(startDate)}`); +const gotoTimetable = async (evaNumber: string, type: "departures" | "arrivals", startDate: string) => { + await goto(`/${evaNumber}/${type}?startDate=${encodeURIComponent(startDate)}`); }; const writeStop = (stop?: Stop, fallbackName: string = ""): string => { From 97e68874b621323d25933b1e85f5ba2c93687109 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 00:03:01 +0100 Subject: [PATCH 05/29] frontend: pass VITE_BACKEND_DOCKER_BASE_URL via docker (for SvelteKit SSR) --- .env.example | 6 +++++- docker-compose.yml | 3 ++- frontend/Dockerfile | 1 + frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts | 6 ++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index e841cdf..a688721 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,7 @@ # Specify the backend URL which is used in the frontend. You can not adjust this to the backend container_name as it is not reachable from the frontend! # Do not add a "/" at the end, as the frontend will add the path to the API endpoint itself. -VITE_BACKEND_BASE_URL=http://localhost:8000 \ No newline at end of file +VITE_BACKEND_BASE_URL=http://localhost:8000 + +# Specify the backend URL which is used in the frontend for SvelteKit SSR. Running in a docker container, you should use the container_name of the backend service. +# Do not add a "/" at the end, as the frontend will add the path to the API endpoint itself. +VITE_BACKEND_DOCKER_BASE_URL=http://navigator-backend:8000 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 6c74076..e8b0432 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,8 @@ services: ports: [ "3000:3000" ] networks: [ "navigator-network" ] environment: - - VITE_BACKEND_BASE_URL=${VITE_BACKEND_BASE_URL} + VITE_BACKEND_BASE_URL: ${VITE_BACKEND_BASE_URL} + VITE_BACKEND_DOCKER_BASE_URL: ${VITE_BACKEND_DOCKER_BASE_URL} navigator-backend: container_name: navigator-backend diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 6dd5c0b..625a57d 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -6,6 +6,7 @@ COPY . . RUN npm install ENV VITE_BACKEND_BASE_URL=http://localhost:8000 +ENV VITE_BACKEND_DOCKER_BASE_URL=http://navigator-backend:8000 RUN npm run build RUN npm prune --production # Remove dev dependencies diff --git a/frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts b/frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts index 7a9d16e..aef29ba 100644 --- a/frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts +++ b/frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts @@ -17,7 +17,8 @@ export const load: PageServerLoad = async ({ params, url }): Promise<{ station: }; const loadStation = async (evaNumber: string): Promise => { - const response = await fetch(`http://navigator-backend:8000/api/v1/stations/${evaNumber}`, { + const baseUrl = import.meta.env.VITE_BACKEND_DOCKER_BASE_URL; + const response = await fetch(`${baseUrl}/api/v1/stations/${evaNumber}`, { method: "GET" }); if (!response.ok) { @@ -32,13 +33,14 @@ const loadStation = async (evaNumber: string): Promise => { }; const loadJourneys = async (evaNumber: string, type: string, startDate: string): Promise => { + const baseUrl = import.meta.env.VITE_BACKEND_DOCKER_BASE_URL; const queryString = new URLSearchParams({ evaNumber: evaNumber, type: type, when: startDate }).toString(); - const response = await fetch(`http://navigator-backend:8000/api/v1/timetable/combined?${queryString}`, { + const response = await fetch(`${baseUrl}/api/v1/timetable/combined?${queryString}`, { method: "GET" }); if (!response.ok) { From de4857151517112bbec430ce1528f3a85287e97a Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 00:03:31 +0100 Subject: [PATCH 06/29] backend: pass redis env via docker --- .env.example | 9 ++++++++- backend/Dockerfile | 17 +++++++---------- docker-compose.yml | 10 +++++----- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index a688721..5507e20 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,11 @@ VITE_BACKEND_BASE_URL=http://localhost:8000 # Specify the backend URL which is used in the frontend for SvelteKit SSR. Running in a docker container, you should use the container_name of the backend service. # Do not add a "/" at the end, as the frontend will add the path to the API endpoint itself. -VITE_BACKEND_DOCKER_BASE_URL=http://navigator-backend:8000 \ No newline at end of file +VITE_BACKEND_DOCKER_BASE_URL=http://navigator-backend:8000 + +# Redis configuration +REDIS_HOST=navigator-redis +REDIS_PORT=6379 +REDIS_DB=0 +REDIS_USER=navigator-stations +REDIS_PASSWORD=password diff --git a/backend/Dockerfile b/backend/Dockerfile index ea43c34..bdd3544 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,21 +1,18 @@ -# Use the official Bun image as the base image FROM oven/bun:latest - -# Set the working directory inside the container WORKDIR /app -# Copy dependency files first to leverage Docker layer caching COPY package.json bun.lockb* ./ -# Install dependencies RUN bun install - -# Copy the rest of your application code (all files and folders) COPY . . + +ENV REDIS_HOST=navigator-redis +ENV REDIS_PORT=6379 +ENV REDIS_DB=0 +ENV REDIS_USER=navigator-stations +ENV REDIS_PASSWORD=password + RUN bun tsoa -# Expose port 8000 (the port your app listens on) EXPOSE 8000 - -# Start the application (adjust the entry point file if necessary) CMD ["bun", "run", "server.ts"] diff --git a/docker-compose.yml b/docker-compose.yml index e8b0432..b6f1600 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,11 +16,11 @@ services: ports: [ "8000:8000" ] networks: [ "navigator-network" ] environment: - REDIS_HOST: navigator-redis - REDIS_PORT: 6379 - REDIS_DB: 0 - REDIS_USER: navigator-stations - REDIS_PASSWORD: password + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + REDIS_DB: ${REDIS_DB} + REDIS_USER: ${REDIS_USER} + REDIS_PASSWORD: ${REDIS_PASSWORD} navigator-redis: container_name: navigator-redis From 318921f4d667d51ab887a76532709d6fbcc58592 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 00:04:28 +0100 Subject: [PATCH 07/29] frontend: fixes Filter being not at the bottom --- frontend/src/routes/[evaNumber]/[type=type]/+page.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte b/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte index b57a05d..726529c 100644 --- a/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte +++ b/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte @@ -44,7 +44,7 @@ }} /> -
+
{data.station.name} @@ -60,7 +60,7 @@ {/each}
-
+
Date: Sat, 15 Feb 2025 00:21:10 +0100 Subject: [PATCH 08/29] backend: add documentation --- backend/controllers/StationsController.ts | 3 ++- .../timetable/BahnhofController.ts | 3 ++- .../timetable/CombinedController.ts | 3 ++- .../controllers/timetable/VendoController.ts | 3 ++- backend/tsoa.json | 24 ++++++++++++++++++- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/backend/controllers/StationsController.ts b/backend/controllers/StationsController.ts index 502059e..c4b780f 100644 --- a/backend/controllers/StationsController.ts +++ b/backend/controllers/StationsController.ts @@ -1,9 +1,10 @@ import { type Station } from "../models/station.ts"; import { mapToEnum, Products } from "../models/products.ts"; -import { Controller, Get, Path, Query, Res, Route, type TsoaResponse } from "tsoa"; +import { Controller, Get, Path, Query, Res, Route, Tags, type TsoaResponse } from "tsoa"; import { getRedisClient } from "../lib/redis.ts"; @Route("stations") +@Tags("Stations") export class StationController extends Controller { @Get() async queryStations( diff --git a/backend/controllers/timetable/BahnhofController.ts b/backend/controllers/timetable/BahnhofController.ts index b50d6a7..9587b4c 100644 --- a/backend/controllers/timetable/BahnhofController.ts +++ b/backend/controllers/timetable/BahnhofController.ts @@ -1,5 +1,5 @@ import { type RequestType, retrieveBahnhofJourneys } from "./requests.ts"; -import { Controller, Get, Queries, Route } from "tsoa"; +import { Controller, Get, Queries, Route, Tags } from "tsoa"; import type { Journey } from "../../models/connection.ts"; class BahnhofQuery { @@ -10,6 +10,7 @@ class BahnhofQuery { } @Route("timetable/bahnhof") +@Tags("Bahnhof") export class BahnhofController extends Controller { @Get() async getBahnhofJourneys(@Queries() query: BahnhofQuery): Promise { diff --git a/backend/controllers/timetable/CombinedController.ts b/backend/controllers/timetable/CombinedController.ts index 9f3cfe0..3eaa6b3 100644 --- a/backend/controllers/timetable/CombinedController.ts +++ b/backend/controllers/timetable/CombinedController.ts @@ -3,7 +3,7 @@ import { RequestType, retrieveBahnhofJourneys, retrieveCombinedConnections } fro import type { Connection, Journey } from "../../models/connection.ts"; import { isMatching } from "../../lib/merge.ts"; import calculateDuration from "../../lib/time.ts"; -import { Controller, Get, Queries, Res, Route, type TsoaResponse } from "tsoa"; +import { Controller, Get, Queries, Res, Route, Tags, type TsoaResponse } from "tsoa"; class CombinedQuery { evaNumber!: string; @@ -15,6 +15,7 @@ class CombinedQuery { } @Route("timetable/combined") +@Tags("Combined") export class CombinedController extends Controller { @Get() async getCombinedJourneys( diff --git a/backend/controllers/timetable/VendoController.ts b/backend/controllers/timetable/VendoController.ts index 1ee16c5..7df607b 100644 --- a/backend/controllers/timetable/VendoController.ts +++ b/backend/controllers/timetable/VendoController.ts @@ -1,6 +1,6 @@ import { DateTime } from "luxon"; import { Profile, RequestType, retrieveCombinedConnections, retrieveConnections } from "./requests.ts"; -import { Controller, Get, Queries, Res, Route, type TsoaResponse } from "tsoa"; +import { Controller, Get, Queries, Res, Route, Tags, type TsoaResponse } from "tsoa"; import type { Journey } from "../../models/connection.ts"; class VendoQuery { @@ -13,6 +13,7 @@ class VendoQuery { } @Route("timetable/vendo") +@Tags("Vendo") export class VendoController extends Controller { @Get() async getVendoJourneys( diff --git a/backend/tsoa.json b/backend/tsoa.json index 0f1ec75..5e42c9c 100644 --- a/backend/tsoa.json +++ b/backend/tsoa.json @@ -6,7 +6,29 @@ "outputDirectory": "build", "specVersion": 3, "version": "1.0.0", - "basePath": "/api/v1/" + "basePath": "/api/v1/", + "tags": [ + { + "name": "Bahnhof", + "description": "Returns journeys from the DeutscheBahn Bahnhof API. All journeys are returned with a RIS ID" + }, + { + "name": "Vendo", + "description": "Returns journeys from the db-vendo-client@6.3.4. It is possible to select the profile which returns the journeys with the depending ID of the profile", + "externalDocs": { + "description": "See more in the API Documentation", + "url": "https://github.com/public-transport/db-vendo-client" + } + }, + { + "name": "Combined", + "description": "Combines both Bahnhof & Vendo journeys into a single response for better convenience" + }, + { + "name": "Stations", + "description": "Returns all stations from the DeutscheBahn API" + } + ] }, "routes": { "routesDir": "build", From b19bcfe0ac95d8f5069540351c7f7b892ebe93ce Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 00:22:43 +0100 Subject: [PATCH 09/29] backend: updated documentation url path for better NGINX integration --- backend/package.json | 2 +- backend/server.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 0a1508f..afdc68a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,5 +1,5 @@ { - "name": "backend", + "name": "Navigator Backend", "module": "server.ts", "type": "module", "scripts": { diff --git a/backend/server.ts b/backend/server.ts index ca05f44..be360b9 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -10,6 +10,7 @@ app.use(express.json()); app.use(cors({ origin: "*" })); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); +app.get("/api", (req, res) => res.redirect("/api-docs")); RegisterRoutes(app); From e1478139e41517e08fd9c323e3b97a4731df05c9 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 08:32:09 +0100 Subject: [PATCH 10/29] backend: fixes evaNumber being not an integer --- backend/controllers/StationsController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/controllers/StationsController.ts b/backend/controllers/StationsController.ts index c4b780f..ad86a2a 100644 --- a/backend/controllers/StationsController.ts +++ b/backend/controllers/StationsController.ts @@ -20,11 +20,11 @@ export class StationController extends Controller { @Get("/{evaNumber}") async getStationByEvaNumber( - @Path() evaNumber: number, + @Path() evaNumber: string, @Res() badRequestResponse: TsoaResponse<400, { reason: string }> ): Promise { - if (!Number.isInteger(evaNumber)) { - return badRequestResponse(400, { reason: "EvaNumber is not an integer" }); + if (!/^\d+$/.test(evaNumber)) { + return badRequestResponse(400, { reason: "evaNumber is not an integer" }); } const cachedStation = await getCachedStation(evaNumber.toString()); From 5ba5e11d6dc90a972d910844db4c2acee8b87b89 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 10:40:05 +0100 Subject: [PATCH 11/29] backend: fixes export, better error message handling --- backend/controllers/StationsController.ts | 4 ++-- backend/controllers/timetable/requests.ts | 2 +- backend/lib/mapping.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/controllers/StationsController.ts b/backend/controllers/StationsController.ts index ad86a2a..ecadccd 100644 --- a/backend/controllers/StationsController.ts +++ b/backend/controllers/StationsController.ts @@ -53,12 +53,12 @@ const fetchStation = async (searchTerm: string): Promise => { }); if (!request.ok) { - throw new Error("HTTP Request error occurred"); + throw new Error(`Failed to fetch stations for ${searchTerm}`); } const response = await request.json(); if (!response || !Array.isArray(response)) { - throw new Error("Invalid response format"); + throw new Error(`Response was expected to be an array, but got ${typeof response}`); } return response.map((data: any) => ({ diff --git a/backend/controllers/timetable/requests.ts b/backend/controllers/timetable/requests.ts index 62d2307..7b47ac6 100644 --- a/backend/controllers/timetable/requests.ts +++ b/backend/controllers/timetable/requests.ts @@ -1,7 +1,7 @@ import type { Connection, Journey } from "../../models/connection.ts"; -import mapConnection from "../../lib/mapping.ts"; import { DateTime } from "luxon"; import { mergeConnections } from "../../lib/merge.ts"; +import { mapConnection } from "../../lib/mapping.ts"; export enum RequestType { DEPARTURES = "departures", diff --git a/backend/lib/mapping.ts b/backend/lib/mapping.ts index dc8b6a6..63c2095 100644 --- a/backend/lib/mapping.ts +++ b/backend/lib/mapping.ts @@ -96,4 +96,4 @@ const mapMessages = ( }; }; -export default mapConnection; +export { mapConnection }; From 5b9f323ca8df6d9b1519a92d05aea4ba4de77ac9 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 10:59:42 +0100 Subject: [PATCH 12/29] backend: add SequenceController - fetches the coach sequence of a connection --- .../controllers/journey/SequenceController.ts | 50 +++++++++++++ backend/lib/mapping.ts | 53 ++++++++++++- backend/models/sequence.ts | 75 +++++++++++++++++++ 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 backend/controllers/journey/SequenceController.ts create mode 100644 backend/models/sequence.ts diff --git a/backend/controllers/journey/SequenceController.ts b/backend/controllers/journey/SequenceController.ts new file mode 100644 index 0000000..ba9e77a --- /dev/null +++ b/backend/controllers/journey/SequenceController.ts @@ -0,0 +1,50 @@ +import { Controller, Get, Queries, Res, Route, Tags, type TsoaResponse } from "tsoa"; +import { DateTime } from "luxon"; +import { mapCoachSequence } from "../../lib/mapping.ts"; + +class SequenceQuery { + lineDetails!: string; + evaNumber!: string; + date!: string; +} + +@Route("journey/sequence") +@Tags("Coach Sequences") +export class SequenceController extends Controller { + @Get() + async getSequenceById( + @Queries() query: SequenceQuery, + @Res() badRequestResponse: TsoaResponse<400, { reason: string }> + ): Promise { + if (query.lineDetails === "") { + return badRequestResponse(400, { reason: "lineDetails must not be empty" }); + } + if (!/^\d+$/.test(query.evaNumber)) { + return badRequestResponse(400, { reason: "evaNumber is not an integer" }); + } + + const dateValidation = DateTime.fromFormat(query.date, "yyyyMMdd"); + if (!dateValidation.isValid) { + return badRequestResponse(400, { reason: `${dateValidation.invalidExplanation}` }); + } + + return fetchCoachSequence(query); + } +} + +const fetchCoachSequence = async (query: SequenceQuery): Promise => { + const request = await fetch(`https://app.vendo.noncd.db.de/mob/zuglaeufe/${query.lineDetails}/halte/by-abfahrt/${query.evaNumber}_${query.date}/wagenreihung`, { + method: "GET", + headers: { + Accept: "application/x.db.vendo.mob.wagenreihung.v3+json", + "Content-Type": "application/x.db.vendo.mob.wagenreihung.v3+json", + "X-Correlation-ID": crypto.randomUUID() + "_" + crypto.randomUUID() + } + }); + + if (!request.ok) { + throw new Error("Failed to fetch coach sequence"); + } + + return mapCoachSequence(await request.json()); +}; \ No newline at end of file diff --git a/backend/lib/mapping.ts b/backend/lib/mapping.ts index 63c2095..1a99274 100644 --- a/backend/lib/mapping.ts +++ b/backend/lib/mapping.ts @@ -4,6 +4,7 @@ import { DateTime } from "luxon"; import type { Stop } from "../models/station.ts"; import type { Message } from "../models/message.ts"; import { mapToProduct } from "../models/products.ts"; +import type { Sequence } from "../models/sequence.ts"; const mapConnection = (entry: any, type: "departures" | "arrivals", profile: "db" | "dbnav"): Connection => { const delay: number = calculateDuration( @@ -96,4 +97,54 @@ const mapMessages = ( }; }; -export { mapConnection }; +const mapCoachSequence = (entry: any): Sequence => { + return { + track: { + start: { position: entry?.gleis?.start?.position }, + end: { position: entry?.gleis?.ende?.position }, + sections: entry?.gleis?.sektoren?.map((rawSection: any) => ({ + name: rawSection?.bezeichnung, + start: { position: rawSection?.start?.position }, + end: { position: rawSection?.ende?.position }, + gleisabschnittswuerfelPosition: rawSection?.gleisabschnittswuerfelPosition, + firstClass: rawSection?.ersteKlasse + })), + name: entry?.gleis?.bezeichnung + }, + vehicleGroup: entry?.fahrzeuggruppen?.map((rawGroup: any) => ({ + vehicles: rawGroup?.fahrzeuge?.map((rawVehicle: any) => ({ + vehicleType: { + category: rawVehicle?.fahrzeugtyp?.fahrzeugkategorie, + model: rawVehicle?.fahrzeugtyp?.baureihe, + firstClass: rawVehicle?.fahrzeugtyp?.ersteKlasse, + secondClass: rawVehicle?.fahrzeugtyp?.zweiteKlasse + }, + status: rawVehicle?.status, + orientation: rawVehicle?.orientierung, + positionOnTrack: { + start: { position: rawVehicle?.positionAmGleis?.start?.position }, + end: { position: rawVehicle?.positionAmGleis?.ende?.position }, + section: rawVehicle?.positionAmGleis?.sektor + }, + equipment: rawVehicle?.ausstattungsmerkmale?.map((rawEquipment: any) => ({ + type: rawEquipment?.art, + status: rawEquipment?.status + })), + orderNumber: rawVehicle?.ordnungsnummer + })), + tripReference: { + type: rawGroup?.fahrtreferenz?.typ, + line: rawGroup?.fahrtreferenz?.linie, + destination: { name: rawGroup?.fahrtreferenz?.ziel.bezeichnung }, + category: rawGroup?.fahrtreferenz?.gattung, + fahrtNr: rawGroup?.fahrtreferenz?.fahrtnummer + }, + designation: rawGroup?.bezeichnung + })), + direction: entry?.fahrtrichtung === "RECHTS" ? "RIGHT" : "LEFT", + plannedTrack: entry?.gleisSoll, + actualTrack: entry?.gleisVorschau + } +} + +export { mapConnection, mapCoachSequence }; diff --git a/backend/models/sequence.ts b/backend/models/sequence.ts new file mode 100644 index 0000000..57ed9e9 --- /dev/null +++ b/backend/models/sequence.ts @@ -0,0 +1,75 @@ +export interface Sequence { + track: Track; + vehicleGroup?: VehicleGroup[]; + direction?: "RIGHT" | "LEFT"; + plannedTrack?: string; + actualTrack?: string; +} + +export interface Track { + start: { position: number }; + end: { position: number }; + sections: Section[] | []; + name: string; // name of the track +} + +export interface Section { + name: string; + start: { position: number }; + end: { position: number }; + gleisabschnittswuerfelPosition: number; // ?????????????????????? + firstClass: boolean; +} + +export interface VehicleGroup { + vehicles: Vehicle[]; + tripReference: TripReference; + designation: string; +} + +export interface TripReference { + type: string; + line: string; + destination: { name: string; }; + category: string; + fahrtNr: string; +} + +export interface Vehicle { + vehicleType: VehicleType; + status: string; + orientation: string; + positionOnTrack: PositionOnTrack; + equipment: Equipment[]; + orderNumber?: number; // ?? +} + +export interface VehicleType { + category: string; + model: string; + firstClass: boolean; + secondClass: boolean; +} + +export interface PositionOnTrack { + start: { position: number }; + end: { position: number }; + section: string; +} + +/** + * SEATS_SEVERELY_DISABLED + * ZONE_FAMILY + * ZONE_QUIET + * INFO + * CABIN_INFANT + * AIR_CONDITION + * TOILET_WHEELCHAIR + * WHEELCHAIR_SPACE + * SEATS_BAHN_COMFORT + * BIKE_SPACE + */ +export interface Equipment { + type: string; + status: string; +} \ No newline at end of file From a40dc78b2b28ae2ee0503c27fd610ea891c8f450 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 22:53:24 +0100 Subject: [PATCH 13/29] backend: add model descriptions to Sequence --- backend/models/sequence.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/models/sequence.ts b/backend/models/sequence.ts index 57ed9e9..1852a31 100644 --- a/backend/models/sequence.ts +++ b/backend/models/sequence.ts @@ -41,9 +41,17 @@ export interface Vehicle { orientation: string; positionOnTrack: PositionOnTrack; equipment: Equipment[]; - orderNumber?: number; // ?? + orderNumber?: number; // coach number } +/** + * category might be: + * + * LOCOMOTIVE + * DOUBLEDECK_FIRST_CLASS + * DOUBLEDECK_ECONOMY_CLASS + * DOUBLEDECK_CONTROLCAR_ECONOMY_CLASS + */ export interface VehicleType { category: string; model: string; @@ -68,6 +76,7 @@ export interface PositionOnTrack { * WHEELCHAIR_SPACE * SEATS_BAHN_COMFORT * BIKE_SPACE + * ZONE_MULTI_PURPOSE */ export interface Equipment { type: string; From b3640b69e871888f95de0deda7a6eba6770eb0d9 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sat, 15 Feb 2025 22:53:53 +0100 Subject: [PATCH 14/29] frontend: add journey/coach-sequence route --- .../src/components/sequence/Sequence.svelte | 83 +++++++++++++++++++ .../sequence/coaches/CoachEnd.svelte | 17 ++++ .../sequence/coaches/CoachMiddle.svelte | 18 ++++ .../sequence/coaches/CoachStart.svelte | 17 ++++ .../sequence/coaches/Locomotive.svelte | 17 ++++ .../journey/coach-sequence/+error.svelte | 12 +++ .../journey/coach-sequence/+layout.svelte | 20 +++++ .../journey/coach-sequence/+page.server.ts | 32 +++++++ .../journey/coach-sequence/+page.svelte | 30 +++++++ 9 files changed, 246 insertions(+) create mode 100644 frontend/src/components/sequence/Sequence.svelte create mode 100644 frontend/src/components/sequence/coaches/CoachEnd.svelte create mode 100644 frontend/src/components/sequence/coaches/CoachMiddle.svelte create mode 100644 frontend/src/components/sequence/coaches/CoachStart.svelte create mode 100644 frontend/src/components/sequence/coaches/Locomotive.svelte create mode 100644 frontend/src/routes/journey/coach-sequence/+error.svelte create mode 100644 frontend/src/routes/journey/coach-sequence/+layout.svelte create mode 100644 frontend/src/routes/journey/coach-sequence/+page.server.ts create mode 100644 frontend/src/routes/journey/coach-sequence/+page.svelte diff --git a/frontend/src/components/sequence/Sequence.svelte b/frontend/src/components/sequence/Sequence.svelte new file mode 100644 index 0000000..e67f597 --- /dev/null +++ b/frontend/src/components/sequence/Sequence.svelte @@ -0,0 +1,83 @@ + + +
+ +
+ {#each sequence.track.sections as section} + {@const style = calculateLength(section.start.position, section.end.position)} +
+ {section.name} +
+ {/each} +
+ + + {#if sequence.vehicleGroup} +
+ {#each sequence.vehicleGroup as group} + + {#each group.vehicles as vehicle, index (vehicle)} + {@const style = calculateLength( + vehicle.positionOnTrack.start.position, + vehicle.positionOnTrack.end.position + )} + {@const vehicleType = vehicle.vehicleType} +
+ {#if vehicleType.category.includes("LOCOMOTIVE")} + + {:else if hasLocomotive(vehicle, "before")} + + {:else if hasLocomotive(vehicle, "after")} + + {:else} + + {/if} +
+ {/each} + {/each} +
+ {/if} +
\ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/CoachEnd.svelte b/frontend/src/components/sequence/coaches/CoachEnd.svelte new file mode 100644 index 0000000..5c74a91 --- /dev/null +++ b/frontend/src/components/sequence/coaches/CoachEnd.svelte @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/CoachMiddle.svelte b/frontend/src/components/sequence/coaches/CoachMiddle.svelte new file mode 100644 index 0000000..59b7c47 --- /dev/null +++ b/frontend/src/components/sequence/coaches/CoachMiddle.svelte @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/CoachStart.svelte b/frontend/src/components/sequence/coaches/CoachStart.svelte new file mode 100644 index 0000000..619b121 --- /dev/null +++ b/frontend/src/components/sequence/coaches/CoachStart.svelte @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/Locomotive.svelte b/frontend/src/components/sequence/coaches/Locomotive.svelte new file mode 100644 index 0000000..0bbeef3 --- /dev/null +++ b/frontend/src/components/sequence/coaches/Locomotive.svelte @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/routes/journey/coach-sequence/+error.svelte b/frontend/src/routes/journey/coach-sequence/+error.svelte new file mode 100644 index 0000000..e6bdc01 --- /dev/null +++ b/frontend/src/routes/journey/coach-sequence/+error.svelte @@ -0,0 +1,12 @@ + + +
+
+

+ {page.status} +

+

Whoosh! It seems that our provider did not find any coach sequence for this

+
+
diff --git a/frontend/src/routes/journey/coach-sequence/+layout.svelte b/frontend/src/routes/journey/coach-sequence/+layout.svelte new file mode 100644 index 0000000..3b34863 --- /dev/null +++ b/frontend/src/routes/journey/coach-sequence/+layout.svelte @@ -0,0 +1,20 @@ + + +
+ + + + + {@render children()} +
diff --git a/frontend/src/routes/journey/coach-sequence/+page.server.ts b/frontend/src/routes/journey/coach-sequence/+page.server.ts new file mode 100644 index 0000000..cdf8542 --- /dev/null +++ b/frontend/src/routes/journey/coach-sequence/+page.server.ts @@ -0,0 +1,32 @@ +import type { Sequence } from "$models/sequence"; +import { error } from "@sveltejs/kit"; +import { DateTime } from "luxon"; + +export const load = async ({ url }): Promise<{ sequence: Sequence }> => { + const lineDetails = url.searchParams.get("lineDetails"); + const evaNumber = url.searchParams.get("evaNumber"); + const date = url.searchParams.get("date"); + + if (!lineDetails || !evaNumber || !date) { + throw error(400, "Missing required parameters"); + } + + if (!/^\d+$/.test(evaNumber)) { + throw error(400, "evaNumber is not an integer"); + } + + const dateValidation = DateTime.fromFormat(date, "yyyyMMdd"); + if (!dateValidation.isValid) { + return error(400, `${dateValidation.invalidExplanation}`); + } + + const baseUrl = import.meta.env.VITE_BACKEND_DOCKER_BASE_URL; + const request = await fetch(`${baseUrl}/api/v1/journey/sequence?lineDetails=${lineDetails}&evaNumber=${evaNumber}&date=${date}`, { + method: "GET" + }); + if (!request.ok) { + throw error(400, "Failed to fetch coach sequence"); + } + + return { sequence: await request.json() as Sequence }; +} \ No newline at end of file diff --git a/frontend/src/routes/journey/coach-sequence/+page.svelte b/frontend/src/routes/journey/coach-sequence/+page.svelte new file mode 100644 index 0000000..52ed468 --- /dev/null +++ b/frontend/src/routes/journey/coach-sequence/+page.svelte @@ -0,0 +1,30 @@ + + + + +
+ +
From c43b05b98c5819f456c24ca88242a29a168f100a Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 12:52:00 +0100 Subject: [PATCH 15/29] frontend: really fixed Filter being at the bottom this time --- frontend/src/routes/[evaNumber]/[type=type]/+page.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte b/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte index 726529c..0216a92 100644 --- a/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte +++ b/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte @@ -44,13 +44,13 @@ }} /> -
+
{data.station.name}
-
+
{#each data.journeys as journey} {#if !matchesFilter(journey)}{:else if journey.connections.length > 1} @@ -60,7 +60,7 @@ {/each}
-
+
Date: Sun, 16 Feb 2025 15:07:25 +0100 Subject: [PATCH 16/29] frontend: fixed size of SVGs --- frontend/src/components/sequence/coaches/CoachEnd.svelte | 2 +- frontend/src/components/sequence/coaches/CoachMiddle.svelte | 2 +- frontend/src/components/sequence/coaches/CoachStart.svelte | 2 +- frontend/src/components/sequence/coaches/Locomotive.svelte | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/sequence/coaches/CoachEnd.svelte b/frontend/src/components/sequence/coaches/CoachEnd.svelte index 5c74a91..2767fd0 100644 --- a/frontend/src/components/sequence/coaches/CoachEnd.svelte +++ b/frontend/src/components/sequence/coaches/CoachEnd.svelte @@ -1,5 +1,5 @@ diff --git a/frontend/src/components/sequence/coaches/CoachMiddle.svelte b/frontend/src/components/sequence/coaches/CoachMiddle.svelte index 59b7c47..38ada83 100644 --- a/frontend/src/components/sequence/coaches/CoachMiddle.svelte +++ b/frontend/src/components/sequence/coaches/CoachMiddle.svelte @@ -1,5 +1,5 @@ diff --git a/frontend/src/components/sequence/coaches/CoachStart.svelte b/frontend/src/components/sequence/coaches/CoachStart.svelte index 619b121..2954185 100644 --- a/frontend/src/components/sequence/coaches/CoachStart.svelte +++ b/frontend/src/components/sequence/coaches/CoachStart.svelte @@ -1,5 +1,5 @@ diff --git a/frontend/src/components/sequence/coaches/Locomotive.svelte b/frontend/src/components/sequence/coaches/Locomotive.svelte index 0bbeef3..d220847 100644 --- a/frontend/src/components/sequence/coaches/Locomotive.svelte +++ b/frontend/src/components/sequence/coaches/Locomotive.svelte @@ -1,5 +1,5 @@ From 6dd76f6cd158ce36d7ea8607ef0f7fae8ea7e1b8 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 15:07:43 +0100 Subject: [PATCH 17/29] frontend: added Controlcars to Sequence --- .../src/components/sequence/Sequence.svelte | 8 ++++++++ .../sequence/coaches/ControlcarEnd.svelte | 17 +++++++++++++++++ .../sequence/coaches/ControlcarStart.svelte | 17 +++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 frontend/src/components/sequence/coaches/ControlcarEnd.svelte create mode 100644 frontend/src/components/sequence/coaches/ControlcarStart.svelte diff --git a/frontend/src/components/sequence/Sequence.svelte b/frontend/src/components/sequence/Sequence.svelte index e67f597..de7e5bc 100644 --- a/frontend/src/components/sequence/Sequence.svelte +++ b/frontend/src/components/sequence/Sequence.svelte @@ -4,6 +4,8 @@ import CoachStart from "$components/sequence/coaches/CoachStart.svelte"; import CoachEnd from "$components/sequence/coaches/CoachEnd.svelte"; import CoachMiddle from "$components/sequence/coaches/CoachMiddle.svelte"; + import ControlcarStart from "$components/sequence/coaches/ControlcarStart.svelte"; + import ControlcarEnd from "$components/sequence/coaches/ControlcarEnd.svelte"; let { sequence }: { sequence: Sequence } = $props(); let vehicle = $state(sequence.vehicleGroup![0].vehicles[0]); @@ -72,6 +74,12 @@ {:else if hasLocomotive(vehicle, "after")} + {:else if vehicleType.category.includes("CONTROLCAR")} + {#if index === 0} + + {:else if index === group.vehicles.length - 1} + + {/if} {:else} {/if} diff --git a/frontend/src/components/sequence/coaches/ControlcarEnd.svelte b/frontend/src/components/sequence/coaches/ControlcarEnd.svelte new file mode 100644 index 0000000..7d92b0e --- /dev/null +++ b/frontend/src/components/sequence/coaches/ControlcarEnd.svelte @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/ControlcarStart.svelte b/frontend/src/components/sequence/coaches/ControlcarStart.svelte new file mode 100644 index 0000000..d8c77d1 --- /dev/null +++ b/frontend/src/components/sequence/coaches/ControlcarStart.svelte @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file From 18ecca2e392c2389d5a5fe18728ccaad59e3334d Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 15:42:28 +0100 Subject: [PATCH 18/29] frontend: added first class decoration to coaches --- .../src/components/sequence/Sequence.svelte | 16 ++++++-------- .../sequence/coaches/CoachEnd.svelte | 22 ++++++++++++++++--- .../sequence/coaches/CoachMiddle.svelte | 22 ++++++++++++++++--- .../sequence/coaches/CoachStart.svelte | 22 ++++++++++++++++--- .../sequence/coaches/ControlcarEnd.svelte | 22 ++++++++++++++++--- .../sequence/coaches/ControlcarStart.svelte | 22 ++++++++++++++++--- 6 files changed, 102 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/sequence/Sequence.svelte b/frontend/src/components/sequence/Sequence.svelte index de7e5bc..b820834 100644 --- a/frontend/src/components/sequence/Sequence.svelte +++ b/frontend/src/components/sequence/Sequence.svelte @@ -71,17 +71,15 @@ {#if vehicleType.category.includes("LOCOMOTIVE")} {:else if hasLocomotive(vehicle, "before")} - + {:else if hasLocomotive(vehicle, "after")} - - {:else if vehicleType.category.includes("CONTROLCAR")} - {#if index === 0} - - {:else if index === group.vehicles.length - 1} - - {/if} + + {:else if index === 0} + + {:else if index === group.vehicles.length - 1} + {:else} - + {/if}
{/each} diff --git a/frontend/src/components/sequence/coaches/CoachEnd.svelte b/frontend/src/components/sequence/coaches/CoachEnd.svelte index 2767fd0..fe41ef0 100644 --- a/frontend/src/components/sequence/coaches/CoachEnd.svelte +++ b/frontend/src/components/sequence/coaches/CoachEnd.svelte @@ -1,17 +1,33 @@ + + + preserveAspectRatio="xMidYMid meet"> + {#if firstClass} + + {/if} \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/CoachMiddle.svelte b/frontend/src/components/sequence/coaches/CoachMiddle.svelte index 38ada83..0db4225 100644 --- a/frontend/src/components/sequence/coaches/CoachMiddle.svelte +++ b/frontend/src/components/sequence/coaches/CoachMiddle.svelte @@ -1,18 +1,34 @@ + + + preserveAspectRatio="xMidYMid meet"> + {#if firstClass} + + {/if} \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/CoachStart.svelte b/frontend/src/components/sequence/coaches/CoachStart.svelte index 2954185..1017646 100644 --- a/frontend/src/components/sequence/coaches/CoachStart.svelte +++ b/frontend/src/components/sequence/coaches/CoachStart.svelte @@ -1,17 +1,33 @@ + + + preserveAspectRatio="xMidYMid meet"> + {#if firstClass} + + {/if} \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/ControlcarEnd.svelte b/frontend/src/components/sequence/coaches/ControlcarEnd.svelte index 7d92b0e..44b446b 100644 --- a/frontend/src/components/sequence/coaches/ControlcarEnd.svelte +++ b/frontend/src/components/sequence/coaches/ControlcarEnd.svelte @@ -1,17 +1,33 @@ + + + preserveAspectRatio="xMidYMid meet"> + {#if firstClass} + + {/if} \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/ControlcarStart.svelte b/frontend/src/components/sequence/coaches/ControlcarStart.svelte index d8c77d1..2736777 100644 --- a/frontend/src/components/sequence/coaches/ControlcarStart.svelte +++ b/frontend/src/components/sequence/coaches/ControlcarStart.svelte @@ -1,17 +1,33 @@ + + + preserveAspectRatio="xMidYMid meet"> + {#if firstClass} + + {/if} \ No newline at end of file From 609bc144dc5b0b0f09dcb88b199772e64ab6d67b Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 16:09:46 +0100 Subject: [PATCH 19/29] backend: changed fahrtNr type to number --- backend/models/sequence.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/models/sequence.ts b/backend/models/sequence.ts index 1852a31..1512b33 100644 --- a/backend/models/sequence.ts +++ b/backend/models/sequence.ts @@ -32,7 +32,7 @@ export interface TripReference { line: string; destination: { name: string; }; category: string; - fahrtNr: string; + fahrtNr: number; } export interface Vehicle { From 0d9241acb14713a0daf842ab4b895b03760548df Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 16:10:05 +0100 Subject: [PATCH 20/29] frontend: added infos about trip --- .../src/components/sequence/Sequence.svelte | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frontend/src/components/sequence/Sequence.svelte b/frontend/src/components/sequence/Sequence.svelte index b820834..8023d1b 100644 --- a/frontend/src/components/sequence/Sequence.svelte +++ b/frontend/src/components/sequence/Sequence.svelte @@ -37,6 +37,21 @@ return adjacentVehicle?.vehicleType.category === "LOCOMOTIVE"; }; + + const tripReference = (): { referenceId: string; destination: string } => { + const referenceIds = new Set(); + const destinations = new Set(); + + sequence?.vehicleGroup?.forEach(({ tripReference: { category, fahrtNr, destination } }) => { + if (fahrtNr) referenceIds.add(`${category} ${fahrtNr}`); + if (destination?.name) destinations.add(destination.name); + }); + + return { + referenceId: Array.from(referenceIds).join(" / "), + destination: Array.from(destinations).join(" / "), + }; + };
@@ -86,4 +101,9 @@ {/each}
{/if} + +
+ {tripReference().referenceId} + {tripReference().destination} +
\ No newline at end of file From 6545182efb1c101aa2e24be35c3bd400b8d9d6ec Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 16:31:51 +0100 Subject: [PATCH 21/29] frontend: added Line Reference in MetaTags --- .../routes/journey/coach-sequence/+page.svelte | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/src/routes/journey/coach-sequence/+page.svelte b/frontend/src/routes/journey/coach-sequence/+page.svelte index 52ed468..b372201 100644 --- a/frontend/src/routes/journey/coach-sequence/+page.svelte +++ b/frontend/src/routes/journey/coach-sequence/+page.svelte @@ -4,10 +4,25 @@ import { MetaTags } from "svelte-meta-tags"; let { data }: PageProps = $props(); + + const tripReference = (): { referenceId: string; destination: string } => { + const referenceIds = new Set(); + const destinations = new Set(); + + data.sequence?.vehicleGroup?.forEach(({ tripReference: { category, fahrtNr, destination } }) => { + if (fahrtNr) referenceIds.add(`${category} ${fahrtNr}`); + if (destination?.name) destinations.add(destination.name); + }); + + return { + referenceId: Array.from(referenceIds).join(" / "), + destination: Array.from(destinations).join(" / "), + }; + }; Date: Sun, 16 Feb 2025 17:00:00 +0100 Subject: [PATCH 22/29] backend: add BISTRO equipment --- backend/models/sequence.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/models/sequence.ts b/backend/models/sequence.ts index 1512b33..b5d7ea8 100644 --- a/backend/models/sequence.ts +++ b/backend/models/sequence.ts @@ -77,6 +77,7 @@ export interface PositionOnTrack { * SEATS_BAHN_COMFORT * BIKE_SPACE * ZONE_MULTI_PURPOSE + * BISTRO */ export interface Equipment { type: string; From abce53c74f5c2d23528305c0e0667340fb7964c6 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 17:02:23 +0100 Subject: [PATCH 23/29] frontend: changed directory of icons --- frontend/src/components/sequence/Sequence.svelte | 14 +++++++------- .../sequence/coaches/{ => small}/CoachEnd.svelte | 2 +- .../coaches/{ => small}/CoachMiddle.svelte | 2 +- .../sequence/coaches/{ => small}/CoachStart.svelte | 2 +- .../coaches/{ => small}/ControlcarEnd.svelte | 2 +- .../coaches/{ => small}/ControlcarStart.svelte | 2 +- .../sequence/coaches/{ => small}/Locomotive.svelte | 7 ++++--- 7 files changed, 16 insertions(+), 15 deletions(-) rename frontend/src/components/sequence/coaches/{ => small}/CoachEnd.svelte (96%) rename frontend/src/components/sequence/coaches/{ => small}/CoachMiddle.svelte (96%) rename frontend/src/components/sequence/coaches/{ => small}/CoachStart.svelte (96%) rename frontend/src/components/sequence/coaches/{ => small}/ControlcarEnd.svelte (96%) rename frontend/src/components/sequence/coaches/{ => small}/ControlcarStart.svelte (96%) rename frontend/src/components/sequence/coaches/{ => small}/Locomotive.svelte (61%) diff --git a/frontend/src/components/sequence/Sequence.svelte b/frontend/src/components/sequence/Sequence.svelte index 8023d1b..f303224 100644 --- a/frontend/src/components/sequence/Sequence.svelte +++ b/frontend/src/components/sequence/Sequence.svelte @@ -1,17 +1,17 @@ -
+
+ + -
+
{#each sequence.track.sections as section} {@const style = calculateLength(section.start.position, section.end.position)}
{#if sequence.vehicleGroup}
- {#each sequence.vehicleGroup as group} + {#each sequence.vehicleGroup as group, groupIndex (group)} - {#each group.vehicles as vehicle, index (vehicle)} + {#each group.vehicles as vehicle, vehicleIndex (vehicle)} {@const style = calculateLength( vehicle.positionOnTrack.start.position, vehicle.positionOnTrack.end.position )} {@const vehicleType = vehicle.vehicleType} -
selectVehicle(groupIndex, vehicleIndex)} > {#if vehicleType.category.includes("LOCOMOTIVE")} @@ -89,20 +102,20 @@ {:else if hasLocomotive(vehicle, "after")} - {:else if index === 0} + {:else if vehicleIndex === 0} - {:else if index === group.vehicles.length - 1} + {:else if vehicleIndex === group.vehicles.length - 1} {:else} {/if} -
+ {/each} {/each}
{/if} -
+
{tripReference().referenceId} {tripReference().destination}
diff --git a/frontend/src/components/sequence/VehicleInfo.svelte b/frontend/src/components/sequence/VehicleInfo.svelte new file mode 100644 index 0000000..a96fc59 --- /dev/null +++ b/frontend/src/components/sequence/VehicleInfo.svelte @@ -0,0 +1,97 @@ + + +{#if !vehicle}{:else} +
+ {#if vehicle?.orderNumber} +
+
+ {#if vehicle?.vehicleType?.category?.includes("LOCOMOTIVE") || vehicle?.vehicleType?.category?.includes("POWERCAR")} + + {:else if vehicle?.vehicleType?.category?.includes("DOUBLEDECK")} + + {:else} + + {/if} + {vehicle?.orderNumber} +
+ +
+ {#if vehicle?.vehicleType?.category?.includes("LOCOMOTIVE")} + Locomotive + {:else if vehicle?.vehicleType?.category?.includes("POWERCAR")} + Powercar + {:else if vehicle?.vehicleType?.firstClass && vehicle?.vehicleType?.secondClass} +
+ 1. + / + 2. Class +
+ {:else if vehicle?.vehicleType?.firstClass} + 1. Class + {:else} + 2. Class + {/if} + {sectionInfo()} +
+
+ {:else} +
+
+ {#if vehicle?.vehicleType?.category?.includes("LOCOMOTIVE") || vehicle?.vehicleType?.category?.includes("POWERCAR")} + + {:else if vehicle?.vehicleType?.category?.includes("DOUBLEDECK")} + + {:else} + + {/if} +
+ +
+ {#if vehicle?.vehicleType?.category?.includes("LOCOMOTIVE")} + Locomotive + {:else if vehicle?.vehicleType?.category?.includes("POWERCAR")} + Powercar + {:else if vehicle?.vehicleType?.firstClass && vehicle?.vehicleType?.secondClass} +
+ 1. + / + 2. Class +
+ {:else if vehicle?.vehicleType?.firstClass} + 1. Class + {:else} + 2. Class + {/if} + {sectionInfo()} +
+
+ {/if} +
+{/if} \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/big/DoubleDeck.svelte b/frontend/src/components/sequence/coaches/big/DoubleDeck.svelte new file mode 100644 index 0000000..cc5f75e --- /dev/null +++ b/frontend/src/components/sequence/coaches/big/DoubleDeck.svelte @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/big/Locomotive.svelte b/frontend/src/components/sequence/coaches/big/Locomotive.svelte new file mode 100644 index 0000000..6a4b7ad --- /dev/null +++ b/frontend/src/components/sequence/coaches/big/Locomotive.svelte @@ -0,0 +1,21 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/sequence/coaches/big/SingleFloor.svelte b/frontend/src/components/sequence/coaches/big/SingleFloor.svelte new file mode 100644 index 0000000..5e7dcc3 --- /dev/null +++ b/frontend/src/components/sequence/coaches/big/SingleFloor.svelte @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file From a91991ecfabd733294827063e0b91c9977ffe54b Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 18:05:32 +0100 Subject: [PATCH 26/29] ci: lint only on PR's --- .github/workflows/linting.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index ce815dd..a8f7475 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -1,8 +1,6 @@ name: Linting Checks on: - push: - branches: ["*"] pull_request: branches: ["*"] workflow_dispatch: From dbfccf87180c7247ecf199015921146909272c93 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 19:00:21 +0100 Subject: [PATCH 27/29] backend: add possibility to download swagger.json --- backend/server.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/server.ts b/backend/server.ts index be360b9..5e3509b 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -10,6 +10,7 @@ app.use(express.json()); app.use(cors({ origin: "*" })); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); +app.get("/api-spec", (req, res) => res.download("./build/swagger.json")); app.get("/api", (req, res) => res.redirect("/api-docs")); RegisterRoutes(app); From 5cf4e3396326192c090bdc977c43182ca299e0cd Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 19:28:34 +0100 Subject: [PATCH 28/29] frontend & backend: use svelte env goofy ah --- .env.example | 9 ++++----- docker-compose.yml | 6 +++--- frontend/Dockerfile | 4 ++-- frontend/src/components/StationSearch.svelte | 4 ++-- .../src/routes/[evaNumber]/[type=type]/+page.server.ts | 7 +++---- .../src/routes/journey/coach-sequence/+page.server.ts | 4 ++-- 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/.env.example b/.env.example index 5507e20..46c8155 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,9 @@ -# Specify the backend URL which is used in the frontend. You can not adjust this to the backend container_name as it is not reachable from the frontend! +# Specify the clientside visible variables which are used in the frontend. If using in a docker-compose, let the 2nd variable be the container_name of the backend service. # Do not add a "/" at the end, as the frontend will add the path to the API endpoint itself. -VITE_BACKEND_BASE_URL=http://localhost:8000 +PUBLIC_BACKEND_BASE_URL=http://localhost:8000 -# Specify the backend URL which is used in the frontend for SvelteKit SSR. Running in a docker container, you should use the container_name of the backend service. -# Do not add a "/" at the end, as the frontend will add the path to the API endpoint itself. -VITE_BACKEND_DOCKER_BASE_URL=http://navigator-backend:8000 +# Specify the serverside visible variables which are used in the frontend. +BACKEND_DOCKER_BASE_URL=http://navigator-backend:8000 # Redis configuration REDIS_HOST=navigator-redis diff --git a/docker-compose.yml b/docker-compose.yml index b6f1600..7e741aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,8 @@ services: ports: [ "3000:3000" ] networks: [ "navigator-network" ] environment: - VITE_BACKEND_BASE_URL: ${VITE_BACKEND_BASE_URL} - VITE_BACKEND_DOCKER_BASE_URL: ${VITE_BACKEND_DOCKER_BASE_URL} + PUBLIC_BACKEND_BASE_URL: ${PUBLIC_BACKEND_BASE_URL} + BACKEND_DOCKER_BASE_URL: ${BACKEND_DOCKER_BASE_URL} navigator-backend: container_name: navigator-backend @@ -38,4 +38,4 @@ networks: driver: bridge volumes: - navigator-stations: \ No newline at end of file + navigator-stations: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 625a57d..1464b11 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -5,8 +5,8 @@ WORKDIR /app COPY . . RUN npm install -ENV VITE_BACKEND_BASE_URL=http://localhost:8000 -ENV VITE_BACKEND_DOCKER_BASE_URL=http://navigator-backend:8000 +ENV PUBLIC_BACKEND_BASE_URL=http://localhost:8000 +ENV BACKEND_DOCKER_BASE_URL=http://navigator-backend:8000 RUN npm run build RUN npm prune --production # Remove dev dependencies diff --git a/frontend/src/components/StationSearch.svelte b/frontend/src/components/StationSearch.svelte index 8242441..c86a645 100644 --- a/frontend/src/components/StationSearch.svelte +++ b/frontend/src/components/StationSearch.svelte @@ -2,6 +2,7 @@ import type { Station } from "$models/station"; import Search from "$components/svg/Search.svelte"; import { onMount } from "svelte"; + import { env } from "$env/dynamic/public"; let { station = $bindable(undefined) }: { station?: Station } = $props(); let open = $state(false); @@ -19,8 +20,7 @@ const searchStations = async (query: string) => { const searchParams = new URLSearchParams({ query }).toString(); - const baseUrl = import.meta.env.VITE_BACKEND_BASE_URL; - const response = await fetch(`${baseUrl}/api/v1/stations?${searchParams}`, { method: "GET" }); + const response = await fetch(`${env.PUBLIC_BACKEND_BASE_URL}/api/v1/stations?${searchParams}`, { method: "GET" }); if (!response.ok) return; const jsonData = await response.json(); diff --git a/frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts b/frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts index aef29ba..e40e784 100644 --- a/frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts +++ b/frontend/src/routes/[evaNumber]/[type=type]/+page.server.ts @@ -3,6 +3,7 @@ import type { Station } from "$models/station"; import { error } from "@sveltejs/kit"; import type { Journey } from "$models/connection"; import { DateTime } from "luxon"; +import { env } from "$env/dynamic/private"; export const load: PageServerLoad = async ({ params, url }): Promise<{ station: Station; journeys: Journey[] }> => { const [station, journeys] = await Promise.all([ @@ -17,8 +18,7 @@ export const load: PageServerLoad = async ({ params, url }): Promise<{ station: }; const loadStation = async (evaNumber: string): Promise => { - const baseUrl = import.meta.env.VITE_BACKEND_DOCKER_BASE_URL; - const response = await fetch(`${baseUrl}/api/v1/stations/${evaNumber}`, { + const response = await fetch(`${env.BACKEND_DOCKER_BASE_URL}/api/v1/stations/${evaNumber}`, { method: "GET" }); if (!response.ok) { @@ -33,14 +33,13 @@ const loadStation = async (evaNumber: string): Promise => { }; const loadJourneys = async (evaNumber: string, type: string, startDate: string): Promise => { - const baseUrl = import.meta.env.VITE_BACKEND_DOCKER_BASE_URL; const queryString = new URLSearchParams({ evaNumber: evaNumber, type: type, when: startDate }).toString(); - const response = await fetch(`${baseUrl}/api/v1/timetable/combined?${queryString}`, { + const response = await fetch(`${env.BACKEND_DOCKER_BASE_URL}/api/v1/timetable/combined?${queryString}`, { method: "GET" }); if (!response.ok) { diff --git a/frontend/src/routes/journey/coach-sequence/+page.server.ts b/frontend/src/routes/journey/coach-sequence/+page.server.ts index cdf8542..dee72e0 100644 --- a/frontend/src/routes/journey/coach-sequence/+page.server.ts +++ b/frontend/src/routes/journey/coach-sequence/+page.server.ts @@ -1,6 +1,7 @@ import type { Sequence } from "$models/sequence"; import { error } from "@sveltejs/kit"; import { DateTime } from "luxon"; +import { env } from "$env/dynamic/private"; export const load = async ({ url }): Promise<{ sequence: Sequence }> => { const lineDetails = url.searchParams.get("lineDetails"); @@ -20,8 +21,7 @@ export const load = async ({ url }): Promise<{ sequence: Sequence }> => { return error(400, `${dateValidation.invalidExplanation}`); } - const baseUrl = import.meta.env.VITE_BACKEND_DOCKER_BASE_URL; - const request = await fetch(`${baseUrl}/api/v1/journey/sequence?lineDetails=${lineDetails}&evaNumber=${evaNumber}&date=${date}`, { + const request = await fetch(`${env.BACKEND_DOCKER_BASE_URL}/api/v1/journey/sequence?lineDetails=${lineDetails}&evaNumber=${evaNumber}&date=${date}`, { method: "GET" }); if (!request.ok) { From 3b2574e9e260ff3c35b9da4b0585b6f5a2bf6f51 Mon Sep 17 00:00:00 2001 From: Christian Knapp Date: Sun, 16 Feb 2025 20:07:52 +0100 Subject: [PATCH 29/29] chore: linting --- .../controllers/journey/SequenceController.ts | 19 ++++---- backend/lib/mapping.ts | 4 +- backend/models/sequence.ts | 4 +- .../src/components/sequence/Sequence.svelte | 31 +++++++------ .../components/sequence/VehicleInfo.svelte | 30 +++++++------ .../sequence/coaches/big/DoubleDeck.svelte | 20 ++++----- .../sequence/coaches/big/Locomotive.svelte | 21 +++++---- .../sequence/coaches/big/SingleFloor.svelte | 20 ++++----- .../sequence/coaches/small/CoachEnd.svelte | 41 ++++++++++-------- .../sequence/coaches/small/CoachMiddle.svelte | 43 +++++++++++-------- .../sequence/coaches/small/CoachStart.svelte | 41 ++++++++++-------- .../coaches/small/ControlcarEnd.svelte | 41 ++++++++++-------- .../coaches/small/ControlcarStart.svelte | 41 ++++++++++-------- .../sequence/coaches/small/Locomotive.svelte | 28 +++++++----- .../[evaNumber]/[type=type]/+page.svelte | 6 +-- .../journey/coach-sequence/+layout.svelte | 2 +- .../journey/coach-sequence/+page.server.ts | 13 +++--- .../journey/coach-sequence/+page.svelte | 4 +- 18 files changed, 224 insertions(+), 185 deletions(-) diff --git a/backend/controllers/journey/SequenceController.ts b/backend/controllers/journey/SequenceController.ts index ba9e77a..2b798e9 100644 --- a/backend/controllers/journey/SequenceController.ts +++ b/backend/controllers/journey/SequenceController.ts @@ -33,18 +33,21 @@ export class SequenceController extends Controller { } const fetchCoachSequence = async (query: SequenceQuery): Promise => { - const request = await fetch(`https://app.vendo.noncd.db.de/mob/zuglaeufe/${query.lineDetails}/halte/by-abfahrt/${query.evaNumber}_${query.date}/wagenreihung`, { - method: "GET", - headers: { - Accept: "application/x.db.vendo.mob.wagenreihung.v3+json", - "Content-Type": "application/x.db.vendo.mob.wagenreihung.v3+json", - "X-Correlation-ID": crypto.randomUUID() + "_" + crypto.randomUUID() + const request = await fetch( + `https://app.vendo.noncd.db.de/mob/zuglaeufe/${query.lineDetails}/halte/by-abfahrt/${query.evaNumber}_${query.date}/wagenreihung`, + { + method: "GET", + headers: { + Accept: "application/x.db.vendo.mob.wagenreihung.v3+json", + "Content-Type": "application/x.db.vendo.mob.wagenreihung.v3+json", + "X-Correlation-ID": crypto.randomUUID() + "_" + crypto.randomUUID() + } } - }); + ); if (!request.ok) { throw new Error("Failed to fetch coach sequence"); } return mapCoachSequence(await request.json()); -}; \ No newline at end of file +}; diff --git a/backend/lib/mapping.ts b/backend/lib/mapping.ts index 1a99274..a9887a7 100644 --- a/backend/lib/mapping.ts +++ b/backend/lib/mapping.ts @@ -144,7 +144,7 @@ const mapCoachSequence = (entry: any): Sequence => { direction: entry?.fahrtrichtung === "RECHTS" ? "RIGHT" : "LEFT", plannedTrack: entry?.gleisSoll, actualTrack: entry?.gleisVorschau - } -} + }; +}; export { mapConnection, mapCoachSequence }; diff --git a/backend/models/sequence.ts b/backend/models/sequence.ts index 04bfed2..7a2fba2 100644 --- a/backend/models/sequence.ts +++ b/backend/models/sequence.ts @@ -30,7 +30,7 @@ export interface VehicleGroup { export interface TripReference { type: string; line: string; - destination: { name: string; }; + destination: { name: string }; category: string; fahrtNr: number; } @@ -82,4 +82,4 @@ export interface PositionOnTrack { export interface Equipment { type: string; status: string; -} \ No newline at end of file +} diff --git a/frontend/src/components/sequence/Sequence.svelte b/frontend/src/components/sequence/Sequence.svelte index 1316b9c..0c1b48d 100644 --- a/frontend/src/components/sequence/Sequence.svelte +++ b/frontend/src/components/sequence/Sequence.svelte @@ -32,12 +32,13 @@ }; }; - const flattenVehicles = $derived(sequence.vehicleGroup - ?.flatMap(g => g.vehicles) - .sort((a, b) => a.positionOnTrack.start.position - b.positionOnTrack.start.position) - || []); + const flattenVehicles = $derived( + sequence.vehicleGroup + ?.flatMap((g) => g.vehicles) + .sort((a, b) => a.positionOnTrack.start.position - b.positionOnTrack.start.position) || [] + ); const hasLocomotive = (currentVehicle: Vehicle, direction: "before" | "after") => { - const index = flattenVehicles.findIndex(v => v === currentVehicle); + const index = flattenVehicles.findIndex((v) => v === currentVehicle); if (index === -1) return false; const adjacentIndex = direction === "before" ? index - 1 : index + 1; @@ -62,17 +63,14 @@ }; -
+
-
+
{#each sequence.track.sections as section} {@const style = calculateLength(section.start.position, section.end.position)} -
+
{section.name}
{/each} @@ -80,7 +78,7 @@ {#if sequence.vehicleGroup} -
+
{#each sequence.vehicleGroup as group, groupIndex (group)} {#each group.vehicles as vehicle, vehicleIndex (vehicle)} @@ -89,9 +87,10 @@ vehicle.positionOnTrack.end.position )} {@const vehicleType = vehicle.vehicleType} - {@const isSelected = selectedVehicle.groupIndex === groupIndex && selectedVehicle.vehicleIndex === vehicleIndex} + {@const isSelected = + selectedVehicle.groupIndex === groupIndex && selectedVehicle.vehicleIndex === vehicleIndex}
{/if} -
+
{tripReference().referenceId} {tripReference().destination}
-
\ No newline at end of file +
diff --git a/frontend/src/components/sequence/VehicleInfo.svelte b/frontend/src/components/sequence/VehicleInfo.svelte index a96fc59..f78447a 100644 --- a/frontend/src/components/sequence/VehicleInfo.svelte +++ b/frontend/src/components/sequence/VehicleInfo.svelte @@ -4,18 +4,21 @@ import DoubleDeck from "$components/sequence/coaches/big/DoubleDeck.svelte"; import SingleFloor from "$components/sequence/coaches/big/SingleFloor.svelte"; - let { track, vehicle }: { track?: Track, vehicle?: Vehicle } = $props(); + let { track, vehicle }: { track?: Track; vehicle?: Vehicle } = $props(); const sectionInfo = (): string => { const vehicleStart = vehicle?.positionOnTrack.start.position ?? 0; const vehicleEnd = vehicle?.positionOnTrack.end.position ?? 0; - const sections = track?.sections.filter(section => !(vehicleEnd < section.start.position || vehicleStart > section.end.position)) ?? []; + const sections = + track?.sections.filter( + (section) => !(vehicleEnd < section.start.position || vehicleStart > section.end.position) + ) ?? []; if (sections.length === 1) { return `Stops in section ${sections[0].name}`; } else if (sections.length > 1) { - return `Stops between sections ${sections.map(section => section.name).join(" & ")}`; + return `Stops between sections ${sections.map((section) => section.name).join(" & ")}`; } else { return "No valid section"; } @@ -23,11 +26,12 @@ {#if !vehicle}{:else} -
{#if vehicle?.orderNumber}
@@ -48,13 +52,13 @@ {:else if vehicle?.vehicleType?.category?.includes("POWERCAR")} Powercar {:else if vehicle?.vehicleType?.firstClass && vehicle?.vehicleType?.secondClass} -
+
1. / 2. Class
{:else if vehicle?.vehicleType?.firstClass} - 1. Class + 1. Class {:else} 2. Class {/if} @@ -79,13 +83,13 @@ {:else if vehicle?.vehicleType?.category?.includes("POWERCAR")} Powercar {:else if vehicle?.vehicleType?.firstClass && vehicle?.vehicleType?.secondClass} -
+
1. / 2. Class
{:else if vehicle?.vehicleType?.firstClass} - 1. Class + 1. Class {:else} 2. Class {/if} @@ -94,4 +98,4 @@
{/if}
-{/if} \ No newline at end of file +{/if} diff --git a/frontend/src/components/sequence/coaches/big/DoubleDeck.svelte b/frontend/src/components/sequence/coaches/big/DoubleDeck.svelte index cc5f75e..fe710e2 100644 --- a/frontend/src/components/sequence/coaches/big/DoubleDeck.svelte +++ b/frontend/src/components/sequence/coaches/big/DoubleDeck.svelte @@ -1,20 +1,20 @@ - + - + \ No newline at end of file + .cls-1 { + @apply fill-text; + } + diff --git a/frontend/src/components/sequence/coaches/big/Locomotive.svelte b/frontend/src/components/sequence/coaches/big/Locomotive.svelte index 6a4b7ad..a5e8d4d 100644 --- a/frontend/src/components/sequence/coaches/big/Locomotive.svelte +++ b/frontend/src/components/sequence/coaches/big/Locomotive.svelte @@ -1,21 +1,20 @@ - + - + \ No newline at end of file + .cls-1 { + @apply fill-text; + } + diff --git a/frontend/src/components/sequence/coaches/big/SingleFloor.svelte b/frontend/src/components/sequence/coaches/big/SingleFloor.svelte index 5e7dcc3..15b6d0d 100644 --- a/frontend/src/components/sequence/coaches/big/SingleFloor.svelte +++ b/frontend/src/components/sequence/coaches/big/SingleFloor.svelte @@ -1,20 +1,20 @@ - + - + \ No newline at end of file + .cls-1 { + @apply fill-text; + } + diff --git a/frontend/src/components/sequence/coaches/small/CoachEnd.svelte b/frontend/src/components/sequence/coaches/small/CoachEnd.svelte index 2cf482d..a7701e6 100644 --- a/frontend/src/components/sequence/coaches/small/CoachEnd.svelte +++ b/frontend/src/components/sequence/coaches/small/CoachEnd.svelte @@ -2,32 +2,37 @@ let { firstClass }: { firstClass: boolean } = $props(); - + - + {#if firstClass} - + {/if} \ No newline at end of file + .cls-2 { + @apply stroke-accent; + stroke-width: 75px; + } + diff --git a/frontend/src/components/sequence/coaches/small/CoachMiddle.svelte b/frontend/src/components/sequence/coaches/small/CoachMiddle.svelte index 1c4e6b7..3237f69 100644 --- a/frontend/src/components/sequence/coaches/small/CoachMiddle.svelte +++ b/frontend/src/components/sequence/coaches/small/CoachMiddle.svelte @@ -2,33 +2,38 @@ let { firstClass }: { firstClass: boolean } = $props(); - + - - + + {#if firstClass} - + {/if} \ No newline at end of file + .cls-2 { + @apply stroke-accent; + stroke-width: 75px; + } + diff --git a/frontend/src/components/sequence/coaches/small/CoachStart.svelte b/frontend/src/components/sequence/coaches/small/CoachStart.svelte index 66b570c..0be5dd1 100644 --- a/frontend/src/components/sequence/coaches/small/CoachStart.svelte +++ b/frontend/src/components/sequence/coaches/small/CoachStart.svelte @@ -2,32 +2,37 @@ let { firstClass }: { firstClass: boolean } = $props(); - + - + {#if firstClass} - + {/if} \ No newline at end of file + .cls-2 { + @apply stroke-accent; + stroke-width: 75px; + } + diff --git a/frontend/src/components/sequence/coaches/small/ControlcarEnd.svelte b/frontend/src/components/sequence/coaches/small/ControlcarEnd.svelte index 539a9a6..e1eae89 100644 --- a/frontend/src/components/sequence/coaches/small/ControlcarEnd.svelte +++ b/frontend/src/components/sequence/coaches/small/ControlcarEnd.svelte @@ -2,32 +2,37 @@ let { firstClass }: { firstClass: boolean } = $props(); - + - + {#if firstClass} - + {/if} \ No newline at end of file + .cls-2 { + @apply stroke-accent; + stroke-width: 75px; + } + diff --git a/frontend/src/components/sequence/coaches/small/ControlcarStart.svelte b/frontend/src/components/sequence/coaches/small/ControlcarStart.svelte index bd9eb67..be18b5b 100644 --- a/frontend/src/components/sequence/coaches/small/ControlcarStart.svelte +++ b/frontend/src/components/sequence/coaches/small/ControlcarStart.svelte @@ -2,32 +2,37 @@ let { firstClass }: { firstClass: boolean } = $props(); - + - + {#if firstClass} - + {/if} \ No newline at end of file + .cls-2 { + @apply stroke-accent; + stroke-width: 75px; + } + diff --git a/frontend/src/components/sequence/coaches/small/Locomotive.svelte b/frontend/src/components/sequence/coaches/small/Locomotive.svelte index 819e5f7..fd57d96 100644 --- a/frontend/src/components/sequence/coaches/small/Locomotive.svelte +++ b/frontend/src/components/sequence/coaches/small/Locomotive.svelte @@ -1,18 +1,24 @@ - + - + \ No newline at end of file + .cls-1 { + @apply fill-none stroke-text; + stroke-miterlimit: 10; + stroke-width: 50px; + } + diff --git a/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte b/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte index 0216a92..5abe6d1 100644 --- a/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte +++ b/frontend/src/routes/[evaNumber]/[type=type]/+page.svelte @@ -44,13 +44,13 @@ }} /> -
+
{data.station.name}
-
+
{#each data.journeys as journey} {#if !matchesFilter(journey)}{:else if journey.connections.length > 1} @@ -60,7 +60,7 @@ {/each}
-
+
-
+
diff --git a/frontend/src/routes/journey/coach-sequence/+page.server.ts b/frontend/src/routes/journey/coach-sequence/+page.server.ts index dee72e0..5631b77 100644 --- a/frontend/src/routes/journey/coach-sequence/+page.server.ts +++ b/frontend/src/routes/journey/coach-sequence/+page.server.ts @@ -21,12 +21,15 @@ export const load = async ({ url }): Promise<{ sequence: Sequence }> => { return error(400, `${dateValidation.invalidExplanation}`); } - const request = await fetch(`${env.BACKEND_DOCKER_BASE_URL}/api/v1/journey/sequence?lineDetails=${lineDetails}&evaNumber=${evaNumber}&date=${date}`, { - method: "GET" - }); + const request = await fetch( + `${env.BACKEND_DOCKER_BASE_URL}/api/v1/journey/sequence?lineDetails=${lineDetails}&evaNumber=${evaNumber}&date=${date}`, + { + method: "GET" + } + ); if (!request.ok) { throw error(400, "Failed to fetch coach sequence"); } - return { sequence: await request.json() as Sequence }; -} \ No newline at end of file + return { sequence: (await request.json()) as Sequence }; +}; diff --git a/frontend/src/routes/journey/coach-sequence/+page.svelte b/frontend/src/routes/journey/coach-sequence/+page.svelte index b372201..38a3e9f 100644 --- a/frontend/src/routes/journey/coach-sequence/+page.svelte +++ b/frontend/src/routes/journey/coach-sequence/+page.svelte @@ -16,7 +16,7 @@ return { referenceId: Array.from(referenceIds).join(" / "), - destination: Array.from(destinations).join(" / "), + destination: Array.from(destinations).join(" / ") }; }; @@ -40,6 +40,6 @@ }} /> -
+