@@ -10,6 +10,7 @@ import { badges } from '../db/schema.js';
1010import { eq } from 'drizzle-orm' ;
1111import { GitHubClient } from '../utils/github-client.js' ;
1212import { BadgeRenderer } from '../components/badge-renderer.js' ;
13+ import { badgeThemes } from '../utils/themes.js' ;
1314import type { BadgeOptions , UserBadgeType } from '../types/badge.types.js' ;
1415
1516/** User badge types that have a corresponding DB column */
@@ -63,7 +64,7 @@ export class BadgeCollectionController {
6364 requiredParams : [ 'username' , 'type' ] ,
6465 optionalParams : [ 'columns' , 'gap' , 'theme' , 'customLabel' , 'labelColor' , 'labelBackground' , 'iconColor' , 'valueColor' , 'valueBackground' ] ,
6566 payload : null ,
66- example : '/badge/collection?username=pphatdev&type=visitors,total-stars,repositories&columns=1' ,
67+ example : '/badge/collection?username=pphatdev&type=visitors,total-stars,repositories&columns=1&theme=galaxy ' ,
6768 } ,
6869 } ;
6970
@@ -127,9 +128,8 @@ export class BadgeCollectionController {
127128 }
128129
129130 private static parseSharedOptions ( req : Request ) : Omit < BadgeOptions , 'type' > {
130- const { theme , customLabel, labelColor, labelBackground, iconColor, valueColor, valueBackground } = req . query ;
131+ const { customLabel, labelColor, labelBackground, iconColor, valueColor, valueBackground } = req . query ;
131132 return {
132- theme : typeof theme === 'string' ? theme : undefined ,
133133 customLabel : typeof customLabel === 'string' ? customLabel : undefined ,
134134 labelColor : typeof labelColor === 'string' ? labelColor : undefined ,
135135 labelBackground : typeof labelBackground === 'string' ? labelBackground : undefined ,
@@ -279,12 +279,13 @@ export class BadgeCollectionController {
279279 options : Omit < BadgeOptions , 'type' > ,
280280 columns : number ,
281281 gap : number ,
282+ themes : string [ ] ,
282283 ) : string {
283284 return [
284285 'badge-collection' ,
285286 `user:${ username } ` ,
286287 `types:${ types . join ( ',' ) } ` ,
287- `theme :${ options . theme ?? 'default' } ` ,
288+ `themes :${ themes . join ( ',' ) } ` ,
288289 `columns:${ columns } ` ,
289290 `gap:${ gap } ` ,
290291 `labelColor:${ options . labelColor ?? '' } ` ,
@@ -355,8 +356,22 @@ export class BadgeCollectionController {
355356 // 4. Parse display options
356357 const sharedOptions = BadgeCollectionController . parseSharedOptions ( req ) ;
357358
359+ // 4a. Parse and validate themes (comma-separated list; cycles across badges)
360+ const rawThemes = BadgeCollectionController . parseQueryList ( req . query . theme ) ;
361+ const themes = rawThemes . length > 0 ? rawThemes : [ 'default' ] ;
362+ const validThemes = Object . keys ( badgeThemes ) ;
363+ const invalidThemes = themes . filter ( ( t ) => ! validThemes . includes ( t ) ) ;
364+ if ( invalidThemes . length > 0 ) {
365+ res . status ( 400 ) . json ( {
366+ error : 'Invalid theme' ,
367+ invalid_themes : invalidThemes ,
368+ valid_themes : validThemes ,
369+ } ) ;
370+ return ;
371+ }
372+
358373 // 5. Check in-memory collection cache
359- const cacheKey = BadgeCollectionController . generateCacheKey ( username , types , sharedOptions , columns , gap ) ;
374+ const cacheKey = BadgeCollectionController . generateCacheKey ( username , types , sharedOptions , columns , gap , themes ) ;
360375 const cached = BadgeCollectionController . svgCache . get ( cacheKey ) ;
361376 if ( cached ) {
362377 if ( req . headers [ 'if-none-match' ] === cached . etag ) {
@@ -379,7 +394,7 @@ export class BadgeCollectionController {
379394
380395 // Render each badge as a standalone SVG
381396 const badgeSvgs = values . map ( ( value , i ) => {
382- const opts : BadgeOptions = { ...sharedOptions , type : types [ i ] } ;
397+ const opts : BadgeOptions = { ...sharedOptions , type : types [ i ] , theme : themes [ i % themes . length ] } ;
383398 return BadgeRenderer . generateBadge ( value , opts ) . trim ( ) ;
384399 } ) ;
385400
0 commit comments