44 NotFoundError ,
55 UnauthorizedError ,
66} from "../errors/index.js" ;
7+ import { apiStatusEnum , type ApiStatus } from "../db/schema.js" ;
78import {
89 parseCursorPagination ,
910 decodeCursor ,
@@ -139,6 +140,23 @@ export function createApisRouter(deps: ApisRouterDeps = {}): Router {
139140 const search =
140141 typeof req . query . search === "string" ? req . query . search : undefined ;
141142
143+ // Validate optional ?status filter against the known enum values.
144+ // The public listing only returns active APIs by default; callers may
145+ // explicitly request a different status (e.g. draft) but unknown values
146+ // are rejected early to avoid silent no-result responses.
147+ const statusParam =
148+ typeof req . query . status === "string" ? req . query . status : undefined ;
149+ if ( statusParam !== undefined ) {
150+ if ( ! apiStatusEnum . includes ( statusParam as ApiStatus ) ) {
151+ next (
152+ new BadRequestError (
153+ `status must be one of: ${ apiStatusEnum . join ( ", " ) } ` ,
154+ ) ,
155+ ) ;
156+ return ;
157+ }
158+ }
159+
142160 const { limit, cursor : rawCursor } = parseCursorPagination ( query ) ;
143161
144162 let cursorDate : Date | undefined ;
@@ -164,6 +182,9 @@ export function createApisRouter(deps: ApisRouterDeps = {}): Router {
164182 category,
165183 search,
166184 cursor : rawCursor ,
185+ // Include status in the key so different status filters are cached
186+ // independently and never collide.
187+ status : statusParam ,
167188 } ) ;
168189 const cached = cache . get ( cacheKey ) ;
169190 if ( cached !== undefined ) {
@@ -179,6 +200,7 @@ export function createApisRouter(deps: ApisRouterDeps = {}): Router {
179200 const fetchLimit = rawCursor ? limit : limit + 1 ;
180201 const rows = await apiRepository . listPublic ( {
181202 limit : fetchLimit ,
203+ status : statusParam as ApiStatus | undefined ,
182204 category,
183205 search,
184206 cursor :
@@ -199,7 +221,34 @@ export function createApisRouter(deps: ApisRouterDeps = {}): Router {
199221 ) ;
200222 }
201223
202- const response = cursorPaginatedResponse ( pageRows , {
224+ // Enrich each row with developer info and endpoints.
225+ // findById returns the full ApiDetails (including joined developer).
226+ // getEndpoints is a lightweight indexed lookup per API.
227+ const enrichedRows = await Promise . all (
228+ pageRows . map ( async ( api ) => {
229+ const [ details , endpoints ] = await Promise . all ( [
230+ apiRepository . findById ( api . id ) ,
231+ apiRepository . getEndpoints ( api . id ) ,
232+ ] ) ;
233+ return {
234+ id : api . id ,
235+ name : api . name ,
236+ description : api . description ,
237+ base_url : api . base_url ,
238+ logo_url : api . logo_url ,
239+ category : api . category ,
240+ status : api . status ,
241+ developer : details ?. developer ?? {
242+ name : null ,
243+ website : null ,
244+ description : null ,
245+ } ,
246+ endpoints,
247+ } ;
248+ } ) ,
249+ ) ;
250+
251+ const response = cursorPaginatedResponse ( enrichedRows , {
203252 limit,
204253 nextCursor,
205254 hasMore,
@@ -212,7 +261,7 @@ export function createApisRouter(deps: ApisRouterDeps = {}): Router {
212261 }
213262 } ) ;
214263
215- router . get ( "/:id" , etagMiddleware , async ( req , res , next ) => {
264+ router . get ( "/:id" , async ( req , res , next ) => {
216265 try {
217266 const id = Number ( req . params . id ) ;
218267
0 commit comments