Skip to content

Commit 047dbca

Browse files
committed
refactor: consolidate duplicated helpers, extract shared factories, drop dead code
Behaviour-preserving structural cleanup across the monorepo. No functional changes except one deliberate display refinement (noted below). Full suite, type-check, and lint pass. Shared helpers / dedup: - core: new `fetchJson(url, { timeoutMs, headers, label, userAgent, nullOnError, errorMessage, init })`; migrated ~31 inline AbortController/AbortSignal fetch sites across integrations onto it, each preserving its exact timeout, headers, User-Agent presence, and throw-vs-null error mode. Added `haversineKm` and routed fuel / overlay-nautical / overlay-natural-events / ev-charging / parking / geocoding off their local great-circle copies. New `safeEqual` util (apps/api) and `useExploreFilters` hook (core). - web: deduped `formatBytes` / `formatRelativeTime` / `formatDistance` onto lib helpers; extracted `floatingChipSx`, `ResultListItem`/`ResultList`, `removeLayerAndSource`/`upsertGeoJsonSource` (layerStyleUtils), `OverlayLegend`, `useStyleSyncedLayer`, admin `ServiceStatusChip`/`MetaRow`, `useDataSourceAttribution`, `PanelDetailHeader`/`SectionLabel`. Config-driven integration factories (exact-match sites only; divergent providers left explicit): - `defineTransitProvider`, `createTidesIntegration`, `createBboxPhotoProvider` (integration-framework / integration-photos); geocoding `resolveEndpoint` + shared types; isochrone types re-exported from the framework contract. apps/api integration-host: extracted `buildIntegrationContext` / `buildIntegrationDb` / `warnInvalidConfig` so cold start and reload share one context builder; made reload emit `integration.error` + warn on duplicate id, symmetric with cold start. Dead code removed: core `geo.ts` + unused coordinate helpers; apps/api `MemCache` + `road-snap`; web `DirectionsDestinationMarker` + `useIntegrationAttribution`; 4 duplicate `photos-*/types.ts` + 6 duplicate `geocoding-*/types.ts`; duplicate `knowledge-ourairports/csv.ts` (test relocated to ourairports-data); dead apps/api knowledge types. Display refinement: shared `formatBytes` now renders TB-scale sizes as "TB" rather than overflowing into "GB" — the only observable change.
1 parent dc05e24 commit 047dbca

133 files changed

Lines changed: 2168 additions & 2917 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/api/src/integration-host.ts

Lines changed: 206 additions & 267 deletions
Large diffs are not rendered by default.

apps/api/src/routes/data-manager.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { timingSafeEqual } from "node:crypto";
21
import { existsSync, readFileSync } from "node:fs";
32
import { join } from "node:path";
43
import { and, asc, count, desc, eq } from "drizzle-orm";
@@ -7,6 +6,7 @@ import { db } from "../db/index.js";
76
import { dataManagerFeedState, dataManagerJobStages, dataManagerJobs } from "../db/schema.js";
87
import { getProviderHealth } from "../services/provider-health/registry.js";
98
import { requireAdmin } from "../utils/require-admin.js";
9+
import { safeEqual } from "../utils/safe-equal.js";
1010

1111
/**
1212
* `/api/data-manager/transit/*` — operator-facing surface for the self-hosted
@@ -31,13 +31,6 @@ interface AuthResult {
3131
reason?: string;
3232
}
3333

34-
function safeEqual(a: string, b: string): boolean {
35-
const aBuf = Buffer.from(a);
36-
const bBuf = Buffer.from(b);
37-
if (aBuf.length !== bBuf.length) return false;
38-
return timingSafeEqual(aBuf, bBuf);
39-
}
40-
4134
function extractBearerToken(req: FastifyRequest): string | null {
4235
const header = req.headers.authorization;
4336
if (typeof header === "string" && header.startsWith("Bearer ")) {

apps/api/src/services/isochrone/provider.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
1-
export type IsochroneTravelMode = "driving" | "walking" | "cycling";
1+
// The isochrone data types are the canonical contract from
2+
// `@openmapx/integration-framework`; re-exported here so the existing
3+
// `services/isochrone/*` + route imports keep resolving. `IsochroneProvider`
4+
// is the apps/api-internal provider contract (not part of the framework).
5+
import type {
6+
IsochroneContour,
7+
IsochroneGeometry,
8+
IsochroneMultiPolygon,
9+
IsochronePolygon,
10+
IsochroneResult,
11+
IsochroneTravelMode,
12+
} from "@openmapx/integration-framework";
213

3-
export interface IsochronePolygon {
4-
type: "Polygon";
5-
coordinates: number[][][];
6-
}
7-
8-
export interface IsochroneMultiPolygon {
9-
type: "MultiPolygon";
10-
coordinates: number[][][][];
11-
}
12-
13-
export type IsochroneGeometry = IsochronePolygon | IsochroneMultiPolygon;
14-
15-
export interface IsochroneContour {
16-
time: number;
17-
geometry: IsochroneGeometry;
18-
}
19-
20-
export interface IsochroneResult {
21-
origin: [number, number];
22-
mode: IsochroneTravelMode;
23-
contours: IsochroneContour[];
24-
}
14+
export type {
15+
IsochroneContour,
16+
IsochroneGeometry,
17+
IsochroneMultiPolygon,
18+
IsochronePolygon,
19+
IsochroneResult,
20+
IsochroneTravelMode,
21+
};
2522

2623
export interface IsochroneProvider {
2724
isochrone(

apps/api/src/services/knowledge/types.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

apps/api/src/utils/cache.ts

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -122,54 +122,6 @@ export function round(value: number, decimals: number): number {
122122
return Math.round(value * factor) / factor;
123123
}
124124

125-
// In-memory LRU cache with soft/hard TTL for stale-while-revalidate
126-
// Sits in front of Redis as L1: sub-millisecond reads for hot queries.
127-
// Soft TTL: data is "stale" but still served (background refresh triggered).
128-
// Hard TTL: data is evicted, falls through to Redis / upstream.
129-
130-
interface MemEntry<T> {
131-
data: T;
132-
softExpiry: number;
133-
hardExpiry: number;
134-
}
135-
136-
export class MemCache<T> {
137-
private cache = new Map<string, MemEntry<T>>();
138-
private readonly maxSize: number;
139-
140-
constructor(maxSize: number) {
141-
this.maxSize = maxSize;
142-
}
143-
144-
get(key: string): { data: T; stale: boolean } | null {
145-
const entry = this.cache.get(key);
146-
if (!entry) return null;
147-
const now = Date.now();
148-
if (now > entry.hardExpiry) {
149-
this.cache.delete(key);
150-
return null;
151-
}
152-
// Move to end (most recently used)
153-
this.cache.delete(key);
154-
this.cache.set(key, entry);
155-
return { data: entry.data, stale: now > entry.softExpiry };
156-
}
157-
158-
set(key: string, data: T, softTtlMs: number, hardTtlMs: number): void {
159-
// Evict oldest if at capacity
160-
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
161-
const oldest = this.cache.keys().next().value;
162-
if (oldest !== undefined) this.cache.delete(oldest);
163-
}
164-
const now = Date.now();
165-
this.cache.set(key, {
166-
data,
167-
softExpiry: now + softTtlMs,
168-
hardExpiry: now + hardTtlMs,
169-
});
170-
}
171-
}
172-
173125
// Centralized TTL constants
174126

175127
export const TTL = {

apps/api/src/utils/require-admin.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { timingSafeEqual } from "node:crypto";
21
import { fromNodeHeaders } from "better-auth/node";
32
import type { FastifyReply, FastifyRequest } from "fastify";
43
import { auth } from "../auth";
4+
import { safeEqual } from "./safe-equal.js";
55

66
export type AdminSession = NonNullable<Awaited<ReturnType<typeof auth.api.getSession>>>;
77

@@ -44,13 +44,6 @@ function socketPeerAddress(request: FastifyRequest): string | undefined {
4444
*/
4545
const LOCAL_ADMIN_TOKEN_HEADER = "x-openmapx-local-admin";
4646

47-
function safeEqual(a: string, b: string): boolean {
48-
const aBuf = Buffer.from(a);
49-
const bBuf = Buffer.from(b);
50-
if (aBuf.length !== bBuf.length) return false;
51-
return timingSafeEqual(aBuf, bBuf);
52-
}
53-
5447
function getLocalAdminToken(): string | null {
5548
const token = process.env.OPENMAPX_LOCAL_ADMIN_TOKEN?.trim();
5649
return token ? token : null;

apps/api/src/utils/road-snap.ts

Lines changed: 0 additions & 137 deletions
This file was deleted.

apps/api/src/utils/safe-equal.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { timingSafeEqual } from "node:crypto";
2+
3+
/**
4+
* Constant-time string comparison. Returns false immediately on a length
5+
* mismatch (the length is not secret), otherwise compares the bytes without an
6+
* early-out so the timing does not leak how many characters matched.
7+
*/
8+
export function safeEqual(a: string, b: string): boolean {
9+
const aBuf = Buffer.from(a);
10+
const bBuf = Buffer.from(b);
11+
if (aBuf.length !== bBuf.length) return false;
12+
return timingSafeEqual(aBuf, bBuf);
13+
}

apps/web/src/components/admin/activity/AuditLog.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Typography from "@mui/material/Typography";
2323
import { useQuery, useQueryClient } from "@tanstack/react-query";
2424
import { useState } from "react";
2525
import { useEnv } from "@/lib/EnvProvider";
26+
import { relativeTimeFromIso } from "@/lib/formatTime";
2627
import { TableSkeleton } from "../shared/TableSkeleton";
2728
import { ActorCell } from "./ActorCell";
2829

@@ -191,14 +192,6 @@ function ActionChip({ action }: { action: string }) {
191192
);
192193
}
193194

194-
function formatRelativeTime(iso: string): string {
195-
const diff = Date.now() - new Date(iso).getTime();
196-
if (diff < 60000) return "just now";
197-
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
198-
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
199-
return new Date(iso).toLocaleDateString();
200-
}
201-
202195
export function AuditLog() {
203196
const env = useEnv();
204197
const queryClient = useQueryClient();
@@ -427,7 +420,7 @@ export function AuditLog() {
427420
color: "text.secondary",
428421
}}
429422
>
430-
{formatRelativeTime(entry.createdAt)}
423+
{relativeTimeFromIso(entry.createdAt)}
431424
</Typography>
432425
</Tooltip>
433426
</TableCell>

apps/web/src/components/admin/activity/JobList.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Typography from "@mui/material/Typography";
2222
import { useQuery, useQueryClient } from "@tanstack/react-query";
2323
import { useState } from "react";
2424
import { useEnv } from "@/lib/EnvProvider";
25+
import { relativeTimeFromIso } from "@/lib/formatTime";
2526
import { TableSkeleton } from "../shared/TableSkeleton";
2627
import { ActorCell } from "./ActorCell";
2728
import { JobDetail } from "./JobDetail";
@@ -54,14 +55,6 @@ function formatJobType(type: string): string {
5455
return type.replace(/\./g, " › ").replace(/_/g, " ");
5556
}
5657

57-
function formatRelativeTime(iso: string): string {
58-
const diff = Date.now() - new Date(iso).getTime();
59-
if (diff < 60000) return "just now";
60-
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
61-
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
62-
return new Date(iso).toLocaleDateString();
63-
}
64-
6558
function JobRow({ job }: { job: AdminJob }) {
6659
const [expanded, setExpanded] = useState(false);
6760

@@ -118,7 +111,7 @@ function JobRow({ job }: { job: AdminJob }) {
118111
color: "text.secondary",
119112
}}
120113
>
121-
{formatRelativeTime(job.createdAt)}
114+
{relativeTimeFromIso(job.createdAt)}
122115
</Typography>
123116
</Tooltip>
124117
</TableCell>

0 commit comments

Comments
 (0)