The V1 API (legacy API) is now served from the main LIPAS backend, replacing the separate legacy codebase. This document covers implementation details.
PostgreSQL (new LIPAS format)
│
▼
Transform (lipas.backend.api.v1.transform)
│
▼
Elasticsearch (legacy_sports_sites_current)
│
▼
V1 API Response (camelCase, legacy format)
Key design decisions:
- Read-only API: POST/PUT/DELETE not implemented
- Separate ES index: Pre-transformed data in
legacy_sports_sites_current - Transform at index time: Not at query time, for performance
- Malli schemas: Runtime validation of responses
src/clj/lipas/backend/api/v1/
├── routes.clj # Reitit routes, Swagger/OpenAPI specs
├── core.clj # Business logic, ES query orchestration
├── search.clj # Elasticsearch query builders
├── transform.clj # New LIPAS → Legacy format transformation
├── sports_place.clj # Property mappings (prop-mappings, prop-mappings-reverse)
├── handlers.clj # Categories and types endpoint handlers
├── http.clj # Pagination, 206 Partial Content, Link headers
├── locations.clj # Location formatting
└── util.clj # Helper functions
src/cljc/lipas/schema/api/
└── v1.cljc # Malli schemas for V1 API responses
The transform.clj module converts new LIPAS format to legacy format.
| From (new) | To (legacy) | Example |
|---|---|---|
kebab-case keys |
camelCase keys |
lipas-id → sportsPlaceId |
| ISO8601 dates | Legacy timestamp | 2019-08-29T12:55:30.259Z → 2019-08-29 15:55:30.259 |
| Enum keys | Localized strings | "city-technical-services" → "Kunta / tekninen toimi" |
| 3D coordinates | 2D coordinates | [lon, lat, elev] → [lon, lat] |
| Property keys | Legacy keys | 150+ mappings in prop-mappings |
These intentional "bugs" are preserved for backwards compatibility:
ligthingtypo (notlighting)pool1LengthMMwith double M (pool 1 length)- Golf area (type 1650) → Golf point (type 1620) conversion
location.locationIdhardcoded to0(legacy internal ID)location.sportsPlacesreturns[lipas-id](was 1:many, now 1:1)- Geometry feature IDs (
pointId,routeId, etc.) hardcoded to0
Multimethods in transform.clj handle special cases:
- Ice stadiums (2510, 2520): Extract rink dimensions from
:rinkscollection - Swimming pools (3110, 3130): Extract pool dimensions from
:poolscollection - Golf (1650): Convert area geometry to point (centroid)
Index name: legacy_sports_sites_current (alias)
Key mappings:
{:location.coordinates.wgs84 {:type "geo_point"}
:location.geom-coll {:type "geo_shape"}
:lastModified {:type "date"
:format "yyyy-MM-dd HH:mm:ss.SSS"}}Documents are stored in legacy format (camelCase, legacy property names).
Full reindex of all sports places:
docker compose run backend-index-search --legacyOr from REPL:
(user/reindex-legacy-search!)When save-sports-site! is called, the legacy index is updated synchronously:
;; In lipas.backend.core/save-sports-site!
(if (should-be-in-legacy-index? resp)
(index-legacy-sports-place! search resp :sync)
(delete-from-legacy-index! search (:lipas-id resp)))Indexing rules:
status: "active"or"out-of-service-temporarily"→ indexedstatus: "out-of-service-permanently"or"incorrect-data"→ deleted from index- Draft saves → no sync
All three public entry points route to the same backend /v1/*:
| Entry Point | nginx Rewrite | X-Forwarded-Prefix |
|---|---|---|
api.lipas.fi/v1/* |
direct | /v1 |
lipas.fi/rest/api/* |
→ /v1/* |
/rest/api |
lipas.cc.jyu.fi/api/* |
→ /v1/* |
/api |
The X-Forwarded-Prefix header controls Link header generation for pagination.
Request to lipas.fi/rest/api/sports-places?pageSize=5:
Link: </rest/api/sports-places/?pageSize=5&page=2>; rel="next", ...
- Complete results:
200 OK - Partial results:
206 Partial Contentwith headers:X-total-count: Total number of matching itemsLink: RFC 5988 links (first,prev,next,last)
| Test Suite | Namespace | Description |
|---|---|---|
| Handler tests | lipas.backend.api.v1.handler-test |
Endpoint tests |
| Golden files | lipas.backend.api.v1.golden-files-test |
Validates against 1000 prod responses |
| Transform | lipas.backend.api.v1.transform-test |
Unit tests for transformation |
| Integration | lipas.backend.api.v1.integration-test |
DB → ES → API pipeline |
| HTTP | lipas.backend.api.v1.http-test |
Pagination, base path |
| Index sync | lipas.backend.api.v1.legacy-index-sync-test |
Real-time sync |
# Run all V1 API tests
bb test-ns lipas.backend.api.v1.handler-test \
lipas.backend.api.v1.golden-files-test \
lipas.backend.api.v1.transform-test \
lipas.backend.api.v1.integration-test \
lipas.backend.api.v1.http-test \
lipas.backend.api.v1.legacy-index-sync-test-
Field selection: Sparse responses with
?fields=don't match the full Malli schema (schema validation tests are skipped for these) -
Additional properties: New properties not in the original legacy API may be included. This is not a breaking change.
-
Coordinate precision: Minor floating-point differences in TM35FIN coordinates compared to the original implementation.