(config: CatalogApiConfig)
| 58 | // ── Router factory ────────────────────────────────────────────── |
| 59 | |
| 60 | export function createCatalogApiRouter(config: CatalogApiConfig): Router { |
| 61 | const router = Router(); |
| 62 | const { requireAuth: authMiddleware, requireAdmin: adminAuthMiddleware } = config; |
| 63 | |
| 64 | // ── POST /api/registry/resolve ────────────────────────────── |
| 65 | |
| 66 | router.post('/resolve', async (req, res) => { |
| 67 | try { |
| 68 | const parsed = ResolveRequestSchema.safeParse(req.body); |
| 69 | if (!parsed.success) { |
| 70 | return res.status(400).json({ error: 'Invalid request', details: parsed.error.issues }); |
| 71 | } |
| 72 | |
| 73 | const { identifiers, provenance, mode } = parsed.data; |
| 74 | |
| 75 | // resolve mode requires authentication |
| 76 | if (mode === 'resolve' && !req.user) { |
| 77 | return res.status(401).json({ error: 'Authentication required for resolve mode' }); |
| 78 | } |
| 79 | |
| 80 | const memberId = req.user?.email ?? 'anonymous'; |
| 81 | const result = await catalogDb.resolveIdentifiers( |
| 82 | identifiers, |
| 83 | mode as ResolveMode, |
| 84 | memberId, |
| 85 | provenance as Provenance |
| 86 | ); |
| 87 | |
| 88 | return res.json(result); |
| 89 | } catch (err) { |
| 90 | logger.error(`Resolve error: ${err instanceof Error ? err.message : String(err)}`); |
| 91 | return res.status(500).json({ error: 'Internal server error' }); |
| 92 | } |
| 93 | }); |
| 94 | |
| 95 | // ── GET /api/registry/catalog ─────────────────────────────── |
| 96 | |
| 97 | router.get('/catalog', async (req, res) => { |
| 98 | try { |
| 99 | const filters = { |
| 100 | classification: req.query.classification as string | undefined, |
| 101 | source: req.query.source as string | undefined, |
| 102 | status: req.query.status as string | undefined, |
| 103 | identifier_type: req.query.identifier_type as string | undefined, |
| 104 | search: req.query.search as string | undefined, |
| 105 | min_resolves: req.query.min_resolves ? parseInt(req.query.min_resolves as string, 10) : undefined, |
| 106 | active_since: req.query.active_since as string | undefined, |
| 107 | limit: req.query.limit ? parseInt(req.query.limit as string, 10) : undefined, |
| 108 | cursor: req.query.cursor as string | undefined, |
| 109 | }; |
| 110 | |
| 111 | const result = await catalogDb.listProperties(filters); |
| 112 | |
| 113 | // For each property, include its identifiers |
| 114 | const entries = await Promise.all( |
| 115 | result.properties.map(async (prop) => { |
| 116 | const full = await catalogDb.getProperty(prop.property_rid); |
| 117 | return { |
no test coverage detected