diff --git a/bridge/src/lib/config.ts b/bridge/src/lib/config.ts index bf7b5f3..0d25fb6 100644 --- a/bridge/src/lib/config.ts +++ b/bridge/src/lib/config.ts @@ -39,6 +39,27 @@ const ConfigSchema = z.object({ // global (XADD MAXLEN ~). Défaut 10 000 events. Les events plus anciens sont // purgés automatiquement par Redis (mode ~ = approximatif, plus performant). streamMaxLen: z.coerce.number().int().positive().default(10_000), + // Optional slug -> table_id map so callers can use human-friendly slugs + // (e.g. /api/v1/views/table/personne) instead of the numeric Baserow ID. + // Format: JSON object string like '{"personne":609,"formation":610}'. + baserowTableIds: z + .string() + .optional() + .transform((raw) => { + if (!raw) return {} as Record; + try { + const parsed = JSON.parse(raw) as Record; + const out: Record = {}; + for (const [k, v] of Object.entries(parsed)) { + if (typeof v === 'number' && Number.isInteger(v) && v > 0) { + out[k.toLowerCase()] = v; + } + } + return out; + } catch { + return {} as Record; + } + }), }); export type Config = z.infer; @@ -68,6 +89,7 @@ export function loadConfig(): Config { rateLimitMutationMax: process.env.RATE_LIMIT_MUTATION_MAX, rateLimitMutationWindow: process.env.RATE_LIMIT_MUTATION_WINDOW, streamMaxLen: process.env.STREAM_MAXLEN, + baserowTableIds: process.env.BASEROW_TABLE_IDS, }); if (!parsed.success) { diff --git a/bridge/src/routes/views.ts b/bridge/src/routes/views.ts index 5174ab1..4657318 100644 --- a/bridge/src/routes/views.ts +++ b/bridge/src/routes/views.ts @@ -82,6 +82,25 @@ function parseIntParam(raw: string, label: string): number { return n; } +/** + * Resolve a table param to a numeric Baserow ID. Accepts either a digits-only + * string (e.g. "609") or a slug (e.g. "personne") that maps via the + * BASEROW_TABLE_IDS env (parsed in config). Throws a 400 validation error if + * neither resolves. + */ +function resolveTableId(raw: string, container: ReturnType): number { + if (/^\d+$/.test(raw)) { + const n = Number.parseInt(raw, 10); + if (n > 0) return n; + } + const map = container.config.baserowTableIds ?? {}; + const id = map[raw.toLowerCase()]; + if (typeof id === 'number' && id > 0) return id; + throw errors.validation([ + { message: `tableId must be a positive integer or a known slug (got: ${raw})` }, + ]); +} + function parseIntQuery(url: URL, name: string, defaultVal: number, max?: number): number { const raw = url.searchParams.get(name); if (!raw) return defaultVal; @@ -97,8 +116,9 @@ function parseIntQuery(url: URL, name: string, defaultVal: number, max?: number) // --------------------------------------------------------------------------- viewsRoutes.get('/table/:tableId', requireScope('read:tables'), async (c) => { - const tableId = parseIntParam(c.req.param('tableId'), 'tableId'); - const { repos, redis } = getContainer(); + const container = getContainer(); + const tableId = resolveTableId(c.req.param('tableId'), container); + const { repos, redis } = container; const views = await repos.views.listByTable(tableId, redis); @@ -126,7 +146,7 @@ viewsRoutes.get('/:viewId/data', requireScope('read:tables'), async (c) => { if (!tableIdRaw) { throw errors.validation([{ message: 'tableId query param required' }]); } - const tableId = parseIntParam(tableIdRaw, 'tableId'); + const tableId = resolveTableId(tableIdRaw, getContainer()); const result = await repos.views.getViewData(viewId, tableId, { page,