No description
  • Scala 95.5%
  • Shell 2.8%
  • Dockerfile 1.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Charles Ilieff 277415437b
All checks were successful
CI Pipeline / Run Tests (push) Successful in 2m29s
CI Pipeline / Deploy to Coolify (push) Successful in 1s
Measure a reservation's real kilometres against the Traccar tracker (#2)
`trip.distance` was purely declarative: whoever booked the car typed in how far
they drove, nothing checked it, and 4 of the 22 existing reservations have NULL
there. The tracker has been logging since 2026-06-19, so the real figure is
knowable. A pass now records it in new gps_* columns beside the declared one,
which is never overwritten — /api/trips/total and the ICS feed are untouched.

The measured window is deliberately NOT the declared start/end: people take the
car early and bring it back late, and measuring [12:00, 12:00] silently loses
those kilometres. So a reservation is widened to its whole days in Europe/Paris
and then clamped to the midpoint of the gap with the nearest non-overlapping
neighbour. A lone midday booking is measured 00:00–24:00; two bookings sharing a
day (08:00–12:00 and 14:00–20:00) split it at 13:00 and their windows touch
exactly, so every kilometre is counted once and none is lost.

That rests on a property of Traccar verified against the live server: its
reports/summary clamps at the window bounds, so adjacent windows partition the
distance exactly. Splitting 2026-08-17 into halves gave 100 + 156000 = 156100 m,
the full day, and the afternoon into thirds gave 72700 + 83300 + 0 = 156000.

Design notes:
- reports/summary beats both alternatives. Against fetching raw positions and
  haversine-summing: one small response instead of thousands of rows, the
  device's own odometer instead of straight lines between sparse fixes, and no
  exposure to the streamed-body hazard TraccarCarLocationService documents.
  Against reports/trips: that sums 100 m lower on the 17th, because its trip
  detection drops sub-threshold movement.
- `Accept: application/json` is mandatory on the report endpoints. Without it
  Traccar answers 200 with an XLSX spreadsheet, which then fails to decode as
  JSON for reasons that look nothing like the cause. The header now lives in
  TraccarHttp, extracted from fetchPositions so the load-bearing "read the body
  inside ZIO.scoped" constraint exists in exactly one place.
- CarDistanceService is a new trait rather than a method on CarLocationService:
  that one answers "where is the car", this one "how far did it go", and keeping
  them apart leaves the parking tests' FakeCarLocationService untouched.
- Only an answer from Traccar stamps gps_computed_at. An empty report (tracker
  dark) is an answer, so it is stamped with no distance and not retried; an
  unreachable Traccar writes nothing, so the next pass picks the reservation up.
- The "is it finished" test is on the widened window end, not end_date —
  widening pushes the window past midnight, so a booking that ended at noon
  today is not measurable until tomorrow.
- saveGpsDistance writes only the gps_* columns: no `updated_at = NOW()`, so a
  measurement never bumps the ICS LAST-MODIFIED stamp, and updateTrip's explicit
  column list means a client PUTting a Trip cannot spoof a measurement.
- The pass reads the whole trip list on purpose: clamping needs the neighbours on
  either side, including already-measured ones and ones before the cut-off.
- First background fiber in the app. catchAllCause sits inside repeat so a
  Traccar outage costs one pass, not the fiber for the process lifetime.

Tests: 8 cases on the window rule (whole-day widening, same-day midpoint split
with touching seams, the existing 00:00–23:59 midnight handover losing only 30 s,
multi-day, overlapping bookings left unclamped, no neighbour, never collapsing),
10 on the pass (cut-off, unclosed window, the widened window actually being
queried, declared value left alone, no re-measure, dark tracker stamped once,
failure retried, force), and 5 on Postgres over the new test_trip_gps schema
including that updated_at does not move and updateTrip cannot overwrite.

Verified end to end against the live tracker: two bookings sharing 2026-08-17
came out 0 km and 156 km over windows meeting at 11:00Z — the day's exact total,
with both declared values untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Reviewed-on: #2
2026-08-18 10:04:32 +00:00
.github/workflows Trigger the Coolify deploy with POST, not GET 2026-08-17 21:04:39 +02:00
.junie Remove unused Gel-related database models, repository implementations, and configuration files following migration to Postgres. 2026-01-24 20:45:50 +01:00
domain Measure a reservation's real kilometres against the Traccar tracker (#2) 2026-08-18 10:04:32 +00:00
domain-test/src Measure a reservation's real kilometres against the Traccar tracker (#2) 2026-08-18 10:04:32 +00:00
native-config Add GraalVM native-image spike: it works, with numbers 2026-08-11 18:16:52 +02:00
remote-storage Drop unused deps and duplicate logback config 2026-08-11 17:51:02 +02:00
remote-storage-test/src Gate calendar endpoint with shared CALENDAR_SECRET token 2026-05-06 16:44:43 +02:00
remote-tracking/src/scala/traccar Measure a reservation's real kilometres against the Traccar tracker (#2) 2026-08-18 10:04:32 +00:00
repository-postgres/src Measure a reservation's real kilometres against the Traccar tracker (#2) 2026-08-18 10:04:32 +00:00
repository-test-postgres/src Measure a reservation's real kilometres against the Traccar tracker (#2) 2026-08-18 10:04:32 +00:00
spike Add GraalVM native-image spike: it works, with numbers 2026-08-11 18:16:52 +02:00
test-support/src/scala/zio/test/sbt chore/update-all-dependencies (#1) 2026-08-11 15:07:06 +00:00
web/src Measure a reservation's real kilometres against the Traccar tracker (#2) 2026-08-18 10:04:32 +00:00
.env.exemple Gate car-location endpoint behind bearer auth 2026-05-11 12:06:42 +02:00
.gitignore Add GraalVM native-image spike: it works, with numbers 2026-08-11 18:16:52 +02:00
.nixpacks.toml chore/update-all-dependencies (#1) 2026-08-11 15:07:06 +00:00
.scalafix.conf first commit 2025-02-08 17:26:12 +01:00
.scalafmt.conf chore/update-all-dependencies (#1) 2026-08-11 15:07:06 +00:00
.sdkmanrc chore/update-all-dependencies (#1) 2026-08-11 15:07:06 +00:00
AGENTS.md chore/update-all-dependencies (#1) 2026-08-11 15:07:06 +00:00
bleep.yaml Drop unused deps and duplicate logback config 2026-08-11 17:51:02 +02:00
CLAUDE.md Install Bleep from its own script so CI survives the ARM64 runner 2026-08-14 15:22:06 +02:00
CONFIGURATION_CHANGES.md Introduce centralized application configuration using ZIO Config 2025-08-08 13:59:06 +02:00
docker-compose.yml Pick the runtime at build time with RUNTIME=jvm|native 2026-08-12 14:01:43 +02:00
Dockerfile Pick the runtime at build time with RUNTIME=jvm|native 2026-08-12 14:01:43 +02:00
GEMINI.md Remove unused Gel-related database models, repository implementations, and configuration files following migration to Postgres. 2026-01-24 20:45:50 +01:00
NATIVE_IMAGE_SPIKE.md Pick the runtime at build time with RUNTIME=jvm|native 2026-08-12 14:01:43 +02:00
qodana.yaml Add qodana.yaml file 2025-08-06 15:36:06 +00:00
README.md test 2026-02-19 22:42:49 +01:00
REFACTORING_SUGGESTIONS.md Simplify and reorganize refactoring suggestions document 2025-09-28 13:58:22 +02:00
test.pdf Add delete functionality to WebDav invoice storage and update tests 2025-08-06 16:53:38 +02:00

Car Sharing Backend

This project is a Scala 3 application using PostgreSQL for database management and Bleep for build automation. Test

Prerequisites

  • Scala 3
  • PostgreSQL
  • Bleep

Setup

Install PostgreSQL

Install PostgreSQL on your system and create a database:

createdb car

Install Bleep

Follow the Bleep installation guide to install Bleep on your system.

Configuration

  1. PostgreSQL Configuration:

    Ensure that PostgreSQL is running and properly configured. Set the following environment variables:

    export DATABASE_URL="jdbc:postgresql://localhost:5432/car"
    export DATABASE_USER="myuser"
    export DATABASE_PASSWORD="mypassword"
    
  2. Application Configuration:

    The application uses ZIO Config to load configuration from application.conf.

    To set up your configuration:

    • Copy the example configuration file:

      cp domain/src/resources/application.conf.example domain/src/resources/application.conf
      
    • Edit the application.conf file with your settings:

      • WebDAV/NextCloud connection settings (username, password, URL)
      • Authentication settings (secret key, token expiration)
    • For production, you can set environment variables instead:

      • NEXTCLOUD_USERNAME - Your NextCloud username
      • NEXTCLOUD_PASSWORD - Your NextCloud password
      • NEXTCLOUD_URL - Your NextCloud URL
      • NEXTCLOUD_BASE_PATH - Base path for files (default: "voiture")
      • AUTH_SECRET_KEY - Secret key for JWT token generation
      • AUTH_TOKEN_EXPIRATION_SECONDS - Token expiration time in seconds

    Note: The application.conf file is excluded from version control to prevent committing sensitive data.

  3. Bleep Configuration:

    Initialize Bleep in your project directory:

    bleep new
    

Usage

Compile the Project

To compile the project, run:

bleep compile web

Run the Project

To run the project, use:

bleep run web

Test the Project

To execute tests, run:

bleep test repository-test-postgres

Project Structure

  • domain/: Core business logic, models, service traits
  • repository-postgres/: PostgreSQL persistence using Doobie + Flyway
  • remote-storage/: WebDAV/NextCloud file storage
  • web/: HTTP API layer using Tapir
  • bleep.yaml: Bleep build configuration file

Database Migrations

This project uses Flyway for database migrations. Migration files are located in repository-postgres/src/resources/db/migration/.

Migrations are automatically applied on application startup.

Additional Resources

License

This project is licensed under the MIT License. See the LICENSE file for details.