PriceStream is WIP a modular Scala backend service for concurrent price ingestion and historical price tracking across multiple platforms. It exposes operational endpoints for the ingestion runs and metrics dashboard visualization.
Should work out of the box. The migration creates the pgcrypto extension; the configured database user
must be permitted to create that extension on a new database.
The system combines:
- REST API for tracking and querying data
- Scheduled ingestion pipeline
- Concurrent provider processing
- Persistent ingestion run tracking
- Historical price tracking (WIP)
- Decoupled dashboard API and API gateway (FastAPI)
- Dashboard frontend to visualize runs data (React)
The HTTP server listens on http://localhost:8080. Routes are mounted in
two groups:
/trackingfor recording prices and retrieving product history/apifor ingestion-run and platform data used by the dashboard
All successful responses are JSON.
| Method | Path | Description |
|---|---|---|
POST |
/tracking/track |
Creates the platform and product if needed, then records a price. |
GET |
/tracking/track/requests/{trackingUrl} |
Returns a tracking request's price history by short code. |
GET |
/tracking/history/{platform}/{externalId} |
Returns the recorded price history for one product. |
GET |
/tracking/products |
Lists all tracked products. |
GET |
/tracking/products?platform={platform} |
Lists products for a platform. The filter is case-insensitive. |
Track a price.
curl -i -X POST http://localhost:8080/tracking/track \
-H "Content-Type: application/json" \
-d '{
"platform": "dummyjson",
"externalId": "1",
}'This returns 201 Created with a short URL for retrieving the tracking
request's history:
{
"status": "Tracking",
"trackedAt": "2026-10-04T12:00:00Z",
"trackingUrl": "aB3dE5fG"
}Retrieve the history for a tracking request. Use the trackingUrl returned
when the request was created:
curl http://localhost:8080/tracking/track/requests/aB3dE5fGExample response:
[
{
"name": "Example product",
"price": 149.50,
"platform": {
"id": 1,
"name": "dummyjson",
"baseUrl": "https://dummyjson.com"
},
"recordedAt": "2026-09-27T12:00:00Z",
"trackedPrice": 159.99
}
]Retrieve a product's history:
curl http://localhost:8080/tracking/history/dummyjson/1Example response:
[
{
"price": 199.99,
"recordedAt": "2026-09-27T12:00:00Z"
}
]An unknown platform or product returns 404 Not Found. Product-list responses
have this shape (the platform value is the platform's internal ID):
[
{
"platform": "1",
"externalId": "1",
"name": "Example product",
"url": "https://example.com/products/1"
}
]| Method | Path | Description |
|---|---|---|
GET |
/api/runs |
Lists all ingestion runs. |
GET |
/api/runs/{id} |
Returns an ingestion run by UUID. |
GET |
/api/runs/platform/{platform} |
Lists ingestion runs for a platform. |
GET |
/api/status/{status} |
Lists ingestion runs with a status. Valid values are Running, Completed, and Failed. |
GET |
/api/platforms |
Lists configured platforms. |
For example:
curl http://localhost:8080/api/runs
curl http://localhost:8080/api/runs/11111111-1111-1111-1111-111111111111
curl http://localhost:8080/api/runs/platform/dummyjson
curl http://localhost:8080/api/status/Completed
curl http://localhost:8080/api/platformsAn ingestion-run response has the following shape. finishedAt and error
are null while applicable data is unavailable:
{
"id": "11111111-1111-1111-1111-111111111111",
"startedAt": "2026-09-27T12:00:00Z",
"finishedAt": "2026-09-27T12:01:00Z",
"status": "Completed",
"error": null,
"products_processed": 42
}/api/platforms returns objects such as:
{
"id": 1,
"name": "dummyjson",
"baseUrl": "https://dummyjson.com"
}The list endpoints return arrays of these objects. A missing run, or a
platform/status query that finds no runs or platforms, returns 404 Not Found.
Backend (Scala)
- Cats Effect
- FS2
- Http4s
- Doobie (PostgreSQL)
- Circe
Dashboard
- FastAPI
- PostgreSQL
- React
- JWT-based authentication
All infrastructure components are managed using Resource:
- HTTP server
- Database transactor
- HTTP client
- Background ingestion scheduler
This guarantees safe startup and graceful shutdown.
The ingestion pipeline is implemented as an FS2 stream:
- Initial ingestion at startup
- Recurring ingestion every 10 minutes (
Stream.awakeEvery) - Executed in a dedicated fiber
- Gracefully cancelled on application shutdown
Each ingestion cycle processes providers in parallel:
providers.parTraverse_(ingestFromProvider)This leverages structured concurrency via Cats Effect fibers.
Each ingestion run is persisted with state tracking:
- Running
- Completed
- Failed
Failures are captured using .attempt to prevent scheduler crashes and
ensure the system continues operating.
The backend ingestion service is logically isolated from the dashboard.
The dashboard consumes only exposed REST endpoints secured with JWT
authentication.
This allows independent evolution of:
- Ingestion logic
- API contracts
- Visualization layer
- Application starts
- Resources are initialized (DB, client, server, scheduler)
- Initial ingestion runs
- Scheduler triggers ingestion every 10 minutes
- Dashboard consumes exposed endpoints for visualization
- Bounded concurrency (
parTraverseN) - Rate limiting / throttling
- Retry with exponential backoff
- Metrics and observability (Prometheus)
- Message broker integration (Kafka / RabbitMQ)
- Circuit breaker for external providers
To
- Explore structured concurrency in Scala
- Apply effect-based architecture in real backend systems
- Model clean separation between domain and infrastructure
- Implement safe background processing with lifecycle control