Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
Wiring HTTP du bridge service. 10 endpoints livres (cf docs/19 §6.1-6.5) :
- GET /api/v1/personnes (+ /:id, + /:id/dashboard)
- GET /api/v1/formations (+ /:id avec rollups blocs/modules)
- GET /api/v1/projets (+ /:id avec rollups taches)
- POST /api/v1/modules/:id/attribuer (RG-01 -> 422, role/heures invalides -> 400)
- POST /api/v1/interventions (validation role developpeur + heures > 0)
- PATCH /api/v1/attributions/:id/heures-realisees (409 si annule/realise)
Layers ajoutees :
- src/middleware/auth.ts : Bearer brg_*, scopes JSON-encoded BRIDGE_API_TOKENS, admin:* wildcard
- src/middleware/error-handler.ts : BridgeError -> JSON shape standard
- src/lib/container.ts : DI singleton (Baserow + Redis + 9 repos), setContainer testable
- src/lib/http.ts : parseListQuery + parseBody zod helper
- src/repos/baserow-repo.ts : BaseRepo<T> abstrait + 9 sous-classes (mapping Row<->Domain)
- src/routes/{personnes,formations,projets,modules,interventions,attributions}.ts
src/index.ts reecrit : buildApp() + initContainer + auth sur /api/v1/* + ready check Baserow+Redis.
Tests : 163/163 verts (12 suites domain + 8 nouvelles : auth, repos, 6 routes).
Coverage src global : 70.77% (cible 60%). Domain 97.86%, routes 96%, middleware 86%.
Choix : BaseRepo abstrait (pas mega-generic, Ockham) ; FakeRepos in-memory pour tests routes
(pas de testcontainers ici, c'est Bloc 7) ; mapping erreurs domain -> HTTP par message texte
(fragile, sera refactor en DomainError typees au Bloc 3.2).
Hors scope (a venir) :
- Bloc 5 : rate limiting Redis
- Bloc 7 : webhook handlers Baserow + sync bidirec + cache invalidation
- Bloc 3.2 : routes /docmost/*, /sync/*, /rapports/*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
/**
|
|
* DI container — initialise les dependances une seule fois au boot et expose
|
|
* un singleton typed pour les routes. Pour les tests, `setContainer` permet
|
|
* d'injecter un mock complet sans toucher a `getContainer`.
|
|
*/
|
|
|
|
import type { Logger } from 'pino';
|
|
import { BaserowClient } from '../adapters/baserow-client.js';
|
|
import { RedisCache } from '../adapters/redis-cache.js';
|
|
import type { ApiTokenRecord } from '../middleware/auth.js';
|
|
import { parseTokens } from '../middleware/auth.js';
|
|
import { type RepoSet, TABLE_NAMES, type TableIds, buildRepos } from '../repos/baserow-repo.js';
|
|
import type { Config } from './config.js';
|
|
import { logger as rootLogger } from './logger.js';
|
|
|
|
export interface Container {
|
|
config: Config;
|
|
baserow: BaserowClient;
|
|
redis: RedisCache;
|
|
repos: RepoSet;
|
|
tokens: ReadonlyMap<string, ApiTokenRecord>;
|
|
tableIds: TableIds;
|
|
logger: Logger;
|
|
}
|
|
|
|
let _container: Container | null = null;
|
|
|
|
export function getContainer(): Container {
|
|
if (!_container) {
|
|
throw new Error('Container not initialized — call initContainer() first');
|
|
}
|
|
return _container;
|
|
}
|
|
|
|
export function setContainer(c: Container | null): void {
|
|
_container = c;
|
|
}
|
|
|
|
export interface InitOptions {
|
|
config: Config;
|
|
/** Pour tests : skip le resolveTableIds reseau. */
|
|
tableIds?: TableIds;
|
|
/** Pour tests : injecter une implem de Baserow/Redis. */
|
|
baserow?: BaserowClient;
|
|
redis?: RedisCache;
|
|
databaseId?: number;
|
|
}
|
|
|
|
export async function initContainer(opts: InitOptions): Promise<Container> {
|
|
const { config } = opts;
|
|
const baserow =
|
|
opts.baserow ??
|
|
new BaserowClient({
|
|
baseUrl: config.baserowApiUrl,
|
|
token: config.baserowApiToken,
|
|
logger: rootLogger,
|
|
});
|
|
const redis = opts.redis ?? new RedisCache({ url: config.redisUrl, logger: rootLogger });
|
|
|
|
let tableIds: TableIds;
|
|
if (opts.tableIds) {
|
|
tableIds = opts.tableIds;
|
|
} else {
|
|
if (typeof opts.databaseId !== 'number') {
|
|
throw new Error('initContainer: databaseId requis si tableIds non fourni');
|
|
}
|
|
const resolved = await baserow.resolveTableIds(opts.databaseId);
|
|
tableIds = pickTableIds(resolved);
|
|
}
|
|
|
|
const repos = buildRepos(baserow, tableIds, rootLogger);
|
|
const tokens = parseTokens(config.bridgeApiTokens);
|
|
|
|
const container: Container = {
|
|
config,
|
|
baserow,
|
|
redis,
|
|
repos,
|
|
tokens,
|
|
tableIds,
|
|
logger: rootLogger,
|
|
};
|
|
setContainer(container);
|
|
return container;
|
|
}
|
|
|
|
/** Verifie que toutes les tables attendues sont presentes dans le mapping name->id. */
|
|
function pickTableIds(resolved: Record<string, number>): TableIds {
|
|
const out: Partial<TableIds> = {};
|
|
for (const name of TABLE_NAMES) {
|
|
const id = resolved[name];
|
|
if (typeof id !== 'number') {
|
|
throw new Error(`Table Baserow manquante : ${name}`);
|
|
}
|
|
out[name] = id;
|
|
}
|
|
return out as TableIds;
|
|
}
|