Compare commits

...

3 Commits
1.8.2 ... 1.8.3

Author SHA1 Message Date
7910beb5cb chore(release): 1.8.3
All checks were successful
Build release images / build-container (push) Successful in 1m29s
2026-02-20 22:56:51 +01:00
1be8836df1 refactor(ScanController, ResponseScanIntake, RunnerKV): Rename totalDistance to distance for consistency 2026-02-20 22:56:34 +01:00
70d6091a6a fix(RunnerSelfServiceController): Update getStationMe method to use req.stationId instead of header 2026-02-20 22:52:29 +01:00
6 changed files with 36 additions and 24 deletions

View File

@@ -2,9 +2,17 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.8.3](https://git.odit.services/lfk/backend/compare/1.8.2...1.8.3)
- fix(RunnerSelfServiceController): Update getStationMe method to use req.stationId instead of header [`70d6091`](https://git.odit.services/lfk/backend/commit/70d6091a6a8a1a3d532c7648d9af51cdf8735c94)
- refactor(ScanController, ResponseScanIntake, RunnerKV): Rename totalDistance to distance for consistency [`1be8836`](https://git.odit.services/lfk/backend/commit/1be8836df1a7dec7261356faac8d23bc9558df56)
#### [1.8.2](https://git.odit.services/lfk/backend/compare/1.8.1...1.8.2) #### [1.8.2](https://git.odit.services/lfk/backend/compare/1.8.1...1.8.2)
> 20 February 2026
- chore(PERFORMANCE_IDEAS): Remove outdated performance optimization ideas document [`a1a2c27`](https://git.odit.services/lfk/backend/commit/a1a2c2747cda8ad4c049d0d3b188e993daa67a01) - chore(PERFORMANCE_IDEAS): Remove outdated performance optimization ideas document [`a1a2c27`](https://git.odit.services/lfk/backend/commit/a1a2c2747cda8ad4c049d0d3b188e993daa67a01)
- chore(release): 1.8.2 [`1d9c451`](https://git.odit.services/lfk/backend/commit/1d9c451dfbcac105d7440797c32080e30252fd18)
- refactor(Dockerfile): Update build process and entry point for TypeScript application [`3197498`](https://git.odit.services/lfk/backend/commit/3197498ab37221156eef42311e58a4038c3309d1) - refactor(Dockerfile): Update build process and entry point for TypeScript application [`3197498`](https://git.odit.services/lfk/backend/commit/3197498ab37221156eef42311e58a4038c3309d1)
- fix(deps): Add @types/bun dependency to devDependencies [`6caa185`](https://git.odit.services/lfk/backend/commit/6caa1850e3668bbf72912121d2d1923a5e22d6e8) - fix(deps): Add @types/bun dependency to devDependencies [`6caa185`](https://git.odit.services/lfk/backend/commit/6caa1850e3668bbf72912121d2d1923a5e22d6e8)
- fix(CreateUser): Await password hashing in toEntity method [`80e7e79`](https://git.odit.services/lfk/backend/commit/80e7e7939c1ab35da9ece1cd9e6e1002e4e50d3a) - fix(CreateUser): Await password hashing in toEntity method [`80e7e79`](https://git.odit.services/lfk/backend/commit/80e7e7939c1ab35da9ece1cd9e6e1002e4e50d3a)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-backend", "name": "@odit/lfk-backend",
"version": "1.8.2", "version": "1.8.3",
"main": "src/app.ts", "main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend", "repository": "https://git.odit.services/lfk/backend",
"author": { "author": {

View File

@@ -104,17 +104,21 @@ export class RunnerSelfServiceController {
return responseScans; return responseScans;
} }
@Get('/stations/me') @Get('/stations/me')
@UseBefore(ScanAuth) @UseBefore(ScanAuth)
@ResponseSchema(ResponseScanStation) @ResponseSchema(ResponseScanStation)
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) @ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@OnUndefined(ScanStationNotFoundError) @OnUndefined(ScanStationNotFoundError)
@OpenAPI({ description: 'Lists basic information about the station whose token got provided. <br> This includes it\'s associated track.', security: [{ "StationApiToken": [] }] }) @OpenAPI({ description: 'Lists basic information about the station whose token got provided. <br> This includes it\'s associated track.', security: [{ "StationApiToken": [] }] })
async getStationMe(@Req() req: Request) { async getStationMe(@Req() req: Request) {
let scan = await this.stationRepository.findOne({ id: parseInt(req.headers["station_id"].toString()) }, { relations: ['track'] }) // ScanAuth middleware sets req.stationId (not a header)
if (!scan) { throw new ScanStationNotFoundError(); } if (!req.stationId) {
return scan.toResponse(); throw new ScanStationNotFoundError();
} }
let scan = await this.stationRepository.findOne({ id: req.stationId }, { relations: ['track'] })
if (!scan) { throw new ScanStationNotFoundError(); }
return scan.toResponse();
}
@Post('/runners/login') @Post('/runners/login')
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })

View File

@@ -1,14 +1,11 @@
import type { Request } from "express"; import type { Request } from "express";
import { Authorized, Body, Delete, Get, HttpError, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers'; import { Authorized, Body, Delete, Get, HttpError, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnection, getConnectionManager } from 'typeorm'; import { getConnection, getConnectionManager, Repository } from 'typeorm';
import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors'; import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors';
import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
import ScanAuth from '../middlewares/ScanAuth'; import ScanAuth from '../middlewares/ScanAuth';
import { deleteCardEntry, getCardEntry, setCardEntry } from '../nats/CardKV';
import { deleteRunnerEntry, getRunnerEntry, RunnerKVEntry, setRunnerEntry, warmRunner } from '../nats/RunnerKV';
import { getStationEntryById } from '../nats/StationKV';
import { CreateScan } from '../models/actions/create/CreateScan'; import { CreateScan } from '../models/actions/create/CreateScan';
import { CreateTrackScan } from '../models/actions/create/CreateTrackScan'; import { CreateTrackScan } from '../models/actions/create/CreateTrackScan';
import { UpdateScan } from '../models/actions/update/UpdateScan'; import { UpdateScan } from '../models/actions/update/UpdateScan';
@@ -20,6 +17,9 @@ import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScan } from '../models/responses/ResponseScan'; import { ResponseScan } from '../models/responses/ResponseScan';
import { ResponseScanIntake, ResponseScanIntakeRunner } from '../models/responses/ResponseScanIntake'; import { ResponseScanIntake, ResponseScanIntakeRunner } from '../models/responses/ResponseScanIntake';
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan'; import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
import { getCardEntry, setCardEntry } from '../nats/CardKV';
import { deleteRunnerEntry, getRunnerEntry, RunnerKVEntry, setRunnerEntry, warmRunner } from '../nats/RunnerKV';
import { getStationEntryById } from '../nats/StationKV';
@JsonController('/scans') @JsonController('/scans')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ScanController { export class ScanController {
@@ -145,12 +145,12 @@ export class ScanController {
// Compute // Compute
const lapTime = entry.latestTimestamp === 0 ? 0 : now - entry.latestTimestamp; const lapTime = entry.latestTimestamp === 0 ? 0 : now - entry.latestTimestamp;
const valid = minimumLapTime === 0 || lapTime > minimumLapTime; const valid = minimumLapTime === 0 || lapTime > minimumLapTime;
const newDistance = entry.totalDistance + (valid ? trackDistance : 0); const newDistance = entry.distance + (valid ? trackDistance : 0);
const newTimestamp = valid ? now : entry.latestTimestamp; const newTimestamp = valid ? now : entry.latestTimestamp;
const updated: RunnerKVEntry = { const updated: RunnerKVEntry = {
displayName: entry.displayName, displayName: entry.displayName,
totalDistance: newDistance, distance: newDistance,
latestTimestamp: newTimestamp, latestTimestamp: newTimestamp,
}; };
@@ -174,7 +174,7 @@ export class ScanController {
const runnerInfo = new ResponseScanIntakeRunner(); const runnerInfo = new ResponseScanIntakeRunner();
runnerInfo.displayName = entry.displayName; runnerInfo.displayName = entry.displayName;
runnerInfo.totalDistance = newDistance; runnerInfo.distance = newDistance;
response = new ResponseScanIntake(); response = new ResponseScanIntake();
response.accepted = true; response.accepted = true;

View File

@@ -11,7 +11,7 @@ export class ResponseScanIntakeRunner {
displayName: string; displayName: string;
@IsInt() @IsInt()
totalDistance: number; distance: number;
} }
export class ResponseScanIntake { export class ResponseScanIntake {

View File

@@ -14,7 +14,7 @@ export interface RunnerKVEntry {
/** "Firstname Lastname" — middlename omitted. */ /** "Firstname Lastname" — middlename omitted. */
displayName: string; displayName: string;
/** Sum of all valid scan distances in metres. */ /** Sum of all valid scan distances in metres. */
totalDistance: number; distance: number;
/** Unix seconds timestamp of the last valid scan. 0 if none. */ /** Unix seconds timestamp of the last valid scan. 0 if none. */
latestTimestamp: number; latestTimestamp: number;
} }
@@ -93,7 +93,7 @@ export async function deleteRunnerEntry(runnerId: number): Promise<void> {
* scan timestamp from the database, writes the result to KV, and returns it. * scan timestamp from the database, writes the result to KV, and returns it.
* *
* Called on any KV cache miss during the scan intake flow. * Called on any KV cache miss during the scan intake flow.
* Also handles the first-scan-ever case — latestTimestamp=0, totalDistance=0. * Also handles the first-scan-ever case — latestTimestamp=0, distance=0.
*/ */
export async function warmRunner(runnerId: number): Promise<RunnerKVEntry> { export async function warmRunner(runnerId: number): Promise<RunnerKVEntry> {
const connection = getConnection(); const connection = getConnection();
@@ -119,7 +119,7 @@ export async function warmRunner(runnerId: number): Promise<RunnerKVEntry> {
const entry: RunnerKVEntry = { const entry: RunnerKVEntry = {
displayName, displayName,
totalDistance: parseInt(distanceResult?.total ?? '0', 10), distance: parseInt(distanceResult?.total ?? '0', 10),
latestTimestamp: latestScan?.timestamp ?? 0, latestTimestamp: latestScan?.timestamp ?? 0,
}; };
@@ -180,7 +180,7 @@ export async function warmAll(): Promise<void> {
const writePromises = runners.map((runner) => { const writePromises = runners.map((runner) => {
const entry: RunnerKVEntry = { const entry: RunnerKVEntry = {
displayName: `${runner.firstname} ${runner.lastname}`, displayName: `${runner.firstname} ${runner.lastname}`,
totalDistance: distanceMap.get(runner.id) ?? 0, distance: distanceMap.get(runner.id) ?? 0,
latestTimestamp: latestMap.get(runner.id) ?? 0, latestTimestamp: latestMap.get(runner.id) ?? 0,
}; };
return setRunnerEntry(runner.id, entry); return setRunnerEntry(runner.id, entry);