@@ -102,6 +102,7 @@ app.use(
102102 }
103103 } ,
104104 credentials : true ,
105+ exposedHeaders : [ 'X-Total-Count' , 'X-RateLimit-Limit' , 'X-RateLimit-Remaining' , 'X-RateLimit-Reset' , 'Retry-After' ] ,
105106 } ) ,
106107) ;
107108
@@ -234,10 +235,15 @@ export function parseCampaignListFilters(query: {
234235 sort ?: CampaignSortField ;
235236 order ?: SortOrder ;
236237} {
237- const VALID_SORT_FIELDS : CampaignSortField [ ] = [ 'newest ' , 'deadline' , 'percentFunded ' , 'totalPledged ' ] ;
238+ const VALID_SORT_FIELDS : CampaignSortField [ ] = [ 'createdAt ' , 'deadline' , 'pledgedAmount ' , 'targetAmount ' ] ;
238239 const VALID_ORDERS : SortOrder [ ] = [ 'asc' , 'desc' ] ;
239240 const rawSort = normalizeQueryValue ( query . sort ) ;
240241 const rawOrder = normalizeQueryValue ( query . order ) ;
242+
243+ if ( rawSort && ! VALID_SORT_FIELDS . includes ( rawSort as CampaignSortField ) ) {
244+ throw new AppError ( `Invalid sort field: ${ rawSort } . Supported fields: ${ VALID_SORT_FIELDS . join ( ', ' ) } ` , 400 , 'INVALID_SORT_FIELD' ) ;
245+ }
246+
241247 return {
242248 asset : normalizeAssetFilter ( query . asset ) ,
243249 status : normalizeStatusFilter ( query . status ) ,
@@ -351,8 +357,10 @@ app.get('/api/campaigns', (req: Request, res: Response) => {
351357
352358 const cached = getCampaignCacheEntry ( cacheKey ) ;
353359 if ( cached ) {
360+ const cachedData = JSON . parse ( cached ) ;
354361 res . setHeader ( 'Cache-Control' , 'max-age=5' ) ;
355362 res . setHeader ( 'X-Cache' , 'HIT' ) ;
363+ res . setHeader ( 'X-Total-Count' , String ( cachedData . pagination . total ) ) ;
356364 res . setHeader ( 'Content-Type' , 'application/json' ) ;
357365 res . send ( cached ) ;
358366 return ;
@@ -401,6 +409,7 @@ app.get('/api/campaigns', (req: Request, res: Response) => {
401409
402410 res . setHeader ( 'Cache-Control' , 'max-age=5' ) ;
403411 res . setHeader ( 'X-Cache' , 'MISS' ) ;
412+ res . setHeader ( 'X-Total-Count' , String ( totalCount ) ) ;
404413 res . setHeader ( 'Content-Type' , 'application/json' ) ;
405414 res . send ( responseBody ) ;
406415} ) ;
@@ -447,6 +456,7 @@ app.get('/api/campaigns/:id/pledges', (req: Request, res: Response) => {
447456 Math . ceil ( totalCount / paginationResult . limit ) ,
448457 ) ;
449458
459+ res . setHeader ( 'X-Total-Count' , String ( totalCount ) ) ;
450460 res . json ( {
451461 data : pledges ,
452462 pagination : {
@@ -763,17 +773,12 @@ function printStartupBanner(): void {
763773 const dbPath = process . env . DB_PATH || path . join ( __dirname , '..' , '..' , 'data' , 'campaigns.db' ) ;
764774 const nodeEnv = process . env . NODE_ENV || 'development' ;
765775
766- /* eslint-disable no-console */
767- console . log ( '' ) ;
768- console . log ( '╔════════════════════════════════════════════════════════════╗' ) ;
769- console . log ( '║ Stellar Goal Vault Backend - Starting Up ║' ) ;
770- console . log ( '╠════════════════════════════════════════════════════════════╣' ) ;
771- console . log ( `║ Port: ${ config . port . toString ( ) . padEnd ( 42 ) } ║` ) ;
772- console . log ( `║ Environment: ${ nodeEnv . padEnd ( 42 ) } ║` ) ;
773- console . log ( `║ Database Path: ${ dbPath . padEnd ( 42 ) } ║` ) ;
774- console . log ( '╚════════════════════════════════════════════════════════════╝' ) ;
775- console . log ( '' ) ;
776- /* eslint-enable no-console */
776+ logInfo ( 'startup_banner' , {
777+ message : 'Stellar Goal Vault Backend - Starting Up' ,
778+ port : config . port ,
779+ environment : nodeEnv ,
780+ databasePath : dbPath ,
781+ } , config . logLevel ) ;
777782}
778783
779784export function configureHttpServer ( server : Server ) : Server {
0 commit comments