Appearance
π‘ Backend Hono API Contract Reference β
All backend API routers reside in the [apps/api/src/routes/](file:///Users/ulad/Documents/Pets/BikeTrackV2/bike-maps/apps/api/src/routes/) folder and are powered by Hono combined with Zod Validator middleware. All endpoints (except Strava webhook callbacks) require a Firebase Bearer token: Authorization: Bearer <firebase_uid_token>.
πΊοΈ 1. Routing Service (/routing) β
A. Point-to-Point Path Calculation β
Computes route geometry, elevation, and surface types.
- Method & Path:
POST /routing/calculate - Request Headers:
Authorization: Bearer <token> - Request Body:
typescript
{
waypoints: Array<{ lat: number; lng: number }>; // Min 2 coordinates
profile?: "road" | "gravel" | "mtb" | "commute" | "direct"; // Default: "road"
alternativeIdx?: number;
avoidanceZones?: Array<{
id: string;
coordinates: number[][]; // Bounding coordinates
isInvalid: boolean;
}>;
}- Response (200 OK):
typescript
{
distanceMeters: number;
routeGeometry: {
type: "LineString";
coordinates: Array<[number, number]>; // strict [longitude, latitude]
};
elevationProfile: Array<{
distance: number; // meters from start
elevation: number; // meters above sea level
lat: number;
lng: number;
surfaceType: string; // "asphalt" | "gravel" | "dirt" | "unknown"
}>;
stats: {
elevationGain: number;
elevationLoss: number;
surfaceBreakdown: {
asphalt: number; // percentage
gravel: number;
dirt: number;
unknown: number;
}
};
}B. Round Trip Loop Generation β
Creates random loop routes from a start point matching target distance metrics.
- Method & Path:
POST /routing/loop - Request Body:
typescript
{
start: { lat: number; lng: number };
targetDistanceKm: number;
elevationPreference: "flat" | "hilly" | "indifferent";
directionBias: "clockwise" | "counterclockwise" | "random";
profile: "road" | "gravel" | "mtb";
}- Response (200 OK): Identical structure to point-to-point
/calculateresult.
βοΈ 2. Route Library & Crop Engine (/api/routes) β
A. Crop Route (Anti-Doxing & Trim) β
Removes exact start/end segments to hide home location.
- Method & Path:
PATCH /api/routes/:id/crop - Request Body:
typescript
{
startTrimMeters?: number; // Distance to slice from beginning
endTrimMeters?: number; // Distance to slice from ending
}- Response (200 OK):
typescript
{
success: true,
route: {
id: string;
userId: string;
sourceActivityId: string | null;
name: string;
distance: number; // in kilometers
polyline: string; // encoded cropped polyline string
isEbike: boolean;
createdAt: string;
}
}- Response (400 Bad Request):
typescript
{
error: "Invalid crop distances. Crop must leave at least 100 meters of route."
}π 3. Point of Interest (POI) Service (/api/pois) β
A. Create Point of Interest β
Places a custom marker onto the global coordinate pool or attaches it to a route.
- Method & Path:
POST /api/pois - Request Body:
typescript
{
name: string;
type: "WATER" | "FOOD" | "HAZARD" | "VIEWPOINT" | "CAMPING" | "GENERAL";
coordinates: { lng: number; lat: number } | [number, number]; // GeoJSON standard
description?: string;
scope?: "global" | "track"; // Default: "global"
visibility?: "public" | "private"; // Default: "public"
routeId?: string; // Optional UUID link
}- Response (201 Created):
typescript
{
id: string;
name: string;
type: "water" | "food" | "hazard" | "viewpoint" | "camping" | "general";
source: "user";
coordinates: { lng: number; lat: number };
scope: "global" | "track";
visibility: "public" | "private";
description?: string;
routeId?: string;
}B. List Global POIs (With optional Bounding Box filter) β
Fetches user POIs, with optional screen spatial limits.
- Method & Path:
GET /api/pois - Query Params:
minLng,minLat,maxLng,maxLat(All must be present to activate spatial index search). - Response (200 OK):
typescript
{
items: Array<{
id: string;
name: string;
type: string;
coordinates: { lng: number; lat: number };
scope: string;
visibility: string;
description?: string;
}>
}C. List POIs Attached to Route β
- Method & Path:
GET /api/pois/route/:routeId - Response (200 OK):
typescript
{
items: Array<Poi>
}D. Delete POI β
- Method & Path:
DELETE /api/pois/:id - Response (200 OK):
{ "success": true }
E. Bulk Delete POIs β
- Method & Path:
POST /api/pois/bulk-delete - Request Body:
typescript
{
ids: string[]; // Array of POI UUIDs
}- Response (200 OK):
{ "success": true }
π² 4. Virtual Garage & Component Wear (/api/garage) β
A. List Garage Bicycles β
- Method & Path:
GET /api/garage - Response (200 OK):
typescript
{
bikes: Array<{
id: string;
name: string;
brand: string;
model: string;
type: string; // Road, Gravel, MTB, E-Bike
stravaGearId: string | null;
totalKm: number;
isIgnored: boolean;
}>
}B. Get Components Wear Status β
Fetches all mounted and inventory components with dynamic wear details.
- Method & Path:
GET /api/components - Response (200 OK):
typescript
{
components: Array<{
id: string;
bikeId: string | null;
name: string;
category: "DRIVETRAIN" | "BRAKES" | "WHEELS" | "SUSPENSION" | "COCKPIT" | "ACCESSORIES";
status: "INVENTORY" | "MOUNTED" | "ARCHIVED";
// Dual-wear dynamic counters
maxLifespanKm: number;
currentLifespanKm: number;
maxLifespanHours: number;
currentLifespanHours: number;
serviceIntervalKm: number;
currentServiceKm: number;
wearPercent: number; // Derived: (currentLifespanKm / maxLifespanKm) * 100
}>
}C. Log Component Service Actions β
Logs component services (Clean, Lube, Replace) to reset active wear counters.
- Method & Path:
POST /api/components/service - Request Body:
typescript
{
componentId: string;
actionType: "clean" | "lube" | "replace" | "adjust";
notes?: string;
kmAtService: number;
}- Response (200 OK):
typescript
{
success: true,
logId: string,
wearReset: boolean // True if "replace" action reset metrics to 0
}