@@ -66,30 +66,27 @@ export const getGameSiteUrl = (gameName = ''): string => {
6666}
6767
6868export const getGameSocialLinks = ( game : Game ) => {
69- const subreddit =
70- game . subreddit ?. length
71- ? game . subreddit
72- : game . settings ?. [ 'social::reddit' ] ?. length
73- ? game . settings [ 'social::reddit' ]
74- : game . settings ?. [ 'subreddit' ] ?. length
75- ? game . settings [ 'subreddit' ]
76- : 'biketag'
77-
78- const bluesky =
79- game . bluesky ?. length
80- ? game . bluesky
81- : game . settings ?. [ 'social::bluesky' ] ?. length
82- ? game . settings [ 'social::bluesky' ]
83- : game . settings ?. [ 'bsky' ] ?. length
84- ? game . settings [ 'bsky' ]
85- : 'biketag.bsky.social'
86-
87- const instagramHandle =
88- game . page ?. length
89- ? game . page
90- : game . settings ?. [ 'social::instagram' ] ?. length
91- ? game . settings [ 'social::instagram' ]
92- : ''
69+ const subreddit = game . subreddit ?. length
70+ ? game . subreddit
71+ : game . settings ?. [ 'social::reddit' ] ?. length
72+ ? game . settings [ 'social::reddit' ]
73+ : game . settings ?. [ 'subreddit' ] ?. length
74+ ? game . settings [ 'subreddit' ]
75+ : 'biketag'
76+
77+ const bluesky = game . bluesky ?. length
78+ ? game . bluesky
79+ : game . settings ?. [ 'social::bluesky' ] ?. length
80+ ? game . settings [ 'social::bluesky' ]
81+ : game . settings ?. [ 'bsky' ] ?. length
82+ ? game . settings [ 'bsky' ]
83+ : 'biketag.bsky.social'
84+
85+ const instagramHandle = game . page ?. length
86+ ? game . page
87+ : game . settings ?. [ 'social::instagram' ] ?. length
88+ ? game . settings [ 'social::instagram' ]
89+ : ''
9390
9491 const instagramLink = instagramHandle ?. length
9592 ? instagramHandle . startsWith ( 'http' )
@@ -528,7 +525,7 @@ export const requireGlobalAdmin = (profile: any): boolean => {
528525 * main/index.json, queue/index.json — tag metadata arrays (biketag format)
529526 *
530527 * Round rules for queue/ validation:
531- * found image filename round → current live round
528+ * found image filename round → current live round (also accept current + 1; uploads sometimes use that)
532529 * mystery image filename round → current live round + 1
533530 *
534531 * Orphan found: queue file is a found image for a past round whose main/ --found slot is empty.
@@ -594,6 +591,12 @@ const queuePrimaryImageKeyPattern =
594591const isQueueSizedVariantKey = ( key : string ) : boolean =>
595592 queueSizedVariantKeyPattern . test ( key . split ( '/' ) . pop ( ) ?? '' )
596593
594+ /** Zero-byte folder objects (e.g. `queue/`) that some buckets include in ListObjects results. */
595+ const isStoragePrefixMarkerKey = ( key : string ) : boolean => {
596+ const trimmed = key . replace ( / \/ + $ / , '' )
597+ return trimmed . length > 0 && ! trimmed . includes ( '/' )
598+ }
599+
597600const getAllowedQueueRoundForImage = (
598601 currentTag : Tag | undefined ,
599602 type : 'found' | 'mystery' ,
@@ -602,6 +605,19 @@ const getAllowedQueueRoundForImage = (
602605 return type === 'found' ? currentTag . tagnumber : currentTag . tagnumber + 1
603606}
604607
608+ const isAllowedQueueRoundForImage = (
609+ currentTag : Tag | undefined ,
610+ type : 'found' | 'mystery' ,
611+ imageRound : number ,
612+ ) : boolean => {
613+ const expectedRound = getAllowedQueueRoundForImage ( currentTag , type )
614+ if ( expectedRound === undefined ) return true
615+ if ( imageRound === expectedRound ) return true
616+ // Found images are sometimes keyed at current+1 (same as mystery) during upload/post flows.
617+ if ( type === 'found' && imageRound === expectedRound + 1 ) return true
618+ return false
619+ }
620+
605621const isNormalFoundMysteryPair = ( group : QueueStorageImage [ ] ) : boolean => {
606622 const tagnumbers = [ ...new Set ( group . map ( ( image ) => image . tagnumber ) ) ] . sort ( ( a , b ) => a - b )
607623 if ( tagnumbers . length !== 2 || tagnumbers [ 1 ] - tagnumbers [ 0 ] !== 1 ) return false
@@ -647,12 +663,8 @@ export const queueImageHasVariants = async (
647663 const client = createQueueStorageClient ( region )
648664
649665 try {
650- await client . send (
651- new HeadObjectCommand ( { Bucket : bucket , Key : `queue/${ base } _small.webp` } ) ,
652- )
653- await client . send (
654- new HeadObjectCommand ( { Bucket : bucket , Key : `queue/${ base } _medium.webp` } ) ,
655- )
666+ await client . send ( new HeadObjectCommand ( { Bucket : bucket , Key : `queue/${ base } _small.webp` } ) )
667+ await client . send ( new HeadObjectCommand ( { Bucket : bucket , Key : `queue/${ base } _medium.webp` } ) )
656668 return true
657669 } catch {
658670 return false
@@ -923,10 +935,7 @@ const getMainMysteryImageUrlForRound = (
923935
924936const normalizePlayerName = ( name ?: string ) : string => ( name ?? '' ) . trim ( ) . toLowerCase ( )
925937
926- const getMainTagForRound = (
927- round : number ,
928- main : MainFolderContext ,
929- ) : Tag | undefined => {
938+ const getMainTagForRound = ( round : number , main : MainFolderContext ) : Tag | undefined => {
930939 if ( main . currentTag ?. tagnumber === round ) return main . currentTag
931940 return main . mainTagsByRound . get ( round )
932941}
@@ -1058,7 +1067,12 @@ export const evaluateOrphanedQueueFoundForTarget = (
10581067) : OrphanedQueueFoundCheck => {
10591068 const currentTag = main . currentTag
10601069 if ( image . type !== 'found' ) {
1061- return { structural : false , playerVerified : false , playerConflict : false , reasons : [ 'not a found image' ] }
1070+ return {
1071+ structural : false ,
1072+ playerVerified : false ,
1073+ playerConflict : false ,
1074+ reasons : [ 'not a found image' ] ,
1075+ }
10621076 }
10631077 if ( currentTag ?. tagnumber === undefined ) {
10641078 return {
@@ -1077,9 +1091,7 @@ export const evaluateOrphanedQueueFoundForTarget = (
10771091 playerVerified : false ,
10781092 playerConflict : false ,
10791093 targetRound,
1080- reasons : [
1081- `target round #${ targetRound } is the current or a future round` ,
1082- ] ,
1094+ reasons : [ `target round #${ targetRound } is the current or a future round` ] ,
10831095 }
10841096 }
10851097
@@ -1202,23 +1214,14 @@ export const evaluateOrphanedQueueFoundForMain = (
12021214 playerConflict : false ,
12031215 targetRound : keyRound ,
12041216 reasons : [
1205- isCurrentRoundQueueFoundSubmission (
1206- keyRound ,
1207- image . metadataTagnumber ,
1208- currentTag . tagnumber ,
1209- )
1217+ isCurrentRoundQueueFoundSubmission ( keyRound , image . metadataTagnumber , currentTag . tagnumber )
12101218 ? 'current-round queue submission — not an orphan'
12111219 : 'filename and metadata do not indicate a past-round orphan' ,
12121220 ] ,
12131221 }
12141222 }
12151223
1216- return evaluateOrphanedQueueFoundForTarget (
1217- image ,
1218- orphanTargets [ 0 ] ,
1219- main ,
1220- simulatedQueue ,
1221- )
1224+ return evaluateOrphanedQueueFoundForTarget ( image , orphanTargets [ 0 ] , main , simulatedQueue )
12221225}
12231226
12241227const listQueueObjectKeys = async (
@@ -1237,7 +1240,11 @@ const listQueueObjectKeys = async (
12371240 ContinuationToken : continuationToken ,
12381241 } ) ,
12391242 )
1240- keys . push ( ...( response . Contents ?. map ( ( obj ) => obj . Key ) . filter ( Boolean ) as string [ ] ) ?? [ ] )
1243+ keys . push (
1244+ ...( ( response . Contents ?. map ( ( obj ) => obj . Key )
1245+ . filter ( ( key ) : key is string => ! ! key ?. length && ! isStoragePrefixMarkerKey ( key ) ) ??
1246+ [ ] ) as string [ ] ) ,
1247+ )
12411248 continuationToken = response . NextContinuationToken
12421249 } while ( continuationToken )
12431250
@@ -1270,7 +1277,12 @@ const parseQueueImageKey = (key: string) => {
12701277export const loadQueueStorageImages = async (
12711278 game : string ,
12721279 region : string ,
1273- ) : Promise < { bucket : string ; keys : string [ ] ; images : QueueStorageImage [ ] ; unparsedKeys : string [ ] } > => {
1280+ ) : Promise < {
1281+ bucket : string
1282+ keys : string [ ]
1283+ images : QueueStorageImage [ ]
1284+ unparsedKeys : string [ ]
1285+ } > => {
12741286 const client = createQueueStorageClient ( region )
12751287 const bucket = `${ game . toLowerCase ( ) } -biketag`
12761288 const keys = await listQueueObjectKeys ( client , bucket , 'queue/' )
@@ -1280,7 +1292,12 @@ export const loadQueueStorageImages = async (
12801292 for ( const key of keys ) {
12811293 const parsed = parseQueueImageKey ( key )
12821294 if ( ! parsed ) {
1283- if ( / ^ q u e u e \/ / . test ( key ) && ! key . endsWith ( '/index.json' ) && ! isQueueSizedVariantKey ( key ) ) {
1295+ if (
1296+ / ^ q u e u e \/ / . test ( key ) &&
1297+ ! key . endsWith ( '/index.json' ) &&
1298+ ! isQueueSizedVariantKey ( key ) &&
1299+ ! isStoragePrefixMarkerKey ( key )
1300+ ) {
12841301 unparsedKeys . push ( key )
12851302 }
12861303 continue
@@ -1291,7 +1308,7 @@ export const loadQueueStorageImages = async (
12911308 let playerId : string | undefined
12921309 let mysteryPlayer : string | undefined
12931310 let foundPlayer : string | undefined
1294- let tagnumber = tagnumberFromKey
1311+ const tagnumber = tagnumberFromKey
12951312 let metadataTagnumber : number | undefined
12961313 let title : string | undefined
12971314 let description : string | undefined
@@ -1428,10 +1445,11 @@ export const collectQueueIssuesFromStorage = (
14281445
14291446 for ( const key of unparsedKeys ) {
14301447 const tagnumber = parseTagnumberFromQueueKey ( key ) ?? 0
1448+ const filename = key . split ( '/' ) . pop ( ) || key
14311449 issues . push ( {
14321450 category : 'non-webp' ,
14331451 tagnumber,
1434- issue : `unrecognized queue file: ${ key . split ( '/' ) . pop ( ) } ` ,
1452+ issue : `unrecognized queue file: ${ filename } ` ,
14351453 url : key ,
14361454 } )
14371455 }
@@ -1460,8 +1478,7 @@ export const collectQueueIssuesFromStorage = (
14601478 ? ` — metadata says found for round #${ targetRound } , filename uses new-round #${ keyRound } `
14611479 : keyRound !== targetRound
14621480 ? ` — filename says round #${ keyRound } , main/ is missing found for round #${ targetRound } `
1463- : image . metadataTagnumber !== undefined &&
1464- image . metadataTagnumber !== targetRound
1481+ : image . metadataTagnumber !== undefined && image . metadataTagnumber !== targetRound
14651482 ? ` (metadata lists round #${ image . metadataTagnumber } )`
14661483 : ''
14671484 const finder =
@@ -1495,7 +1512,7 @@ export const collectQueueIssuesFromStorage = (
14951512 if (
14961513 ! orphanedKeys . has ( image . key ) &&
14971514 expectedRound !== undefined &&
1498- image . tagnumber !== expectedRound
1515+ ! isAllowedQueueRoundForImage ( currentTag , image . type , image . tagnumber )
14991516 ) {
15001517 issues . push ( {
15011518 category : 'wrong-round' ,
@@ -1636,11 +1653,7 @@ const loadMainTagIndex = async (gameSlug: string, region: string): Promise<Tag[]
16361653 }
16371654}
16381655
1639- const saveMainTagIndex = async (
1640- gameSlug : string ,
1641- region : string ,
1642- tags : Tag [ ] ,
1643- ) : Promise < void > => {
1656+ const saveMainTagIndex = async ( gameSlug : string , region : string , tags : Tag [ ] ) : Promise < void > => {
16441657 const client = createQueueStorageClient ( region )
16451658 const bucket = `${ gameSlug . toLowerCase ( ) } -biketag`
16461659
@@ -1748,7 +1761,8 @@ export const completeOrphanedQueueFoundMoveToMain = async (
17481761 if ( ! check . structural ) {
17491762 return {
17501763 success : false ,
1751- error : check . reasons . join ( '; ' ) || 'queue found image failed orphaned-main-found validation' ,
1764+ error :
1765+ check . reasons . join ( '; ' ) || 'queue found image failed orphaned-main-found validation' ,
17521766 }
17531767 }
17541768 if ( check . playerConflict ) {
@@ -1894,7 +1908,11 @@ export async function collectQueueIssuesFromTags(
18941908 const imageRound = parseTagnumberFromQueueKey ( storageKey )
18951909 const expectedRound = getAllowedQueueRoundForImage ( currentTag , type )
18961910
1897- if ( expectedRound !== undefined && imageRound !== undefined && imageRound !== expectedRound ) {
1911+ if (
1912+ expectedRound !== undefined &&
1913+ imageRound !== undefined &&
1914+ ! isAllowedQueueRoundForImage ( currentTag , type , imageRound )
1915+ ) {
18981916 issues . push ( {
18991917 category : 'wrong-round' ,
19001918 tagnumber : imageRound ,
@@ -2422,7 +2440,7 @@ export const sendEmailsToAmbassadors = async (
24222440 if ( sendToAdmin ) {
24232441 const biketagAdminEmail = process . env . ADMIN_EMAIL ?? ''
24242442 if ( biketagAdminEmail ?. length ) {
2425- log ( `sending ${ emailName } email to BikeTag Administrator:` , { biketagAdminEmail} , 'info' )
2443+ log ( `sending ${ emailName } email to BikeTag Administrator:` , { biketagAdminEmail } , 'info' )
24262444 emailSent = await sendEmail (
24272445 biketagAdminEmail ,
24282446 emailSubject ,
@@ -2474,10 +2492,10 @@ export const archiveAndClearQueue = async (
24742492 if ( gameResponse . success ) {
24752493 game = gameResponse . data
24762494 } else {
2477- return { results : [ { message : ErrorMessage . GameNotSet , game : undefined } ] , errors : true }
2495+ return { results : [ { message : ErrorMessage . GameNotSet , game : undefined } ] , errors : true }
24782496 }
24792497 }
2480-
2498+
24812499 const imageSource = getImageSource ( game )
24822500
24832501 if ( queuedTags . length && game ) {
@@ -2585,7 +2603,10 @@ export const getActiveQueueForGame = async (
25852603
25862604 log ( 'Evaluating active queue for game' , { game : game . name , autoPostSetting, imageSource } , 'info' )
25872605
2588- if ( ( autoPostSetting && ( game . queuehash ?. length || game . awsRegion ?. length ) ) || approvingAmbassadorIsApproved ) {
2606+ if (
2607+ ( autoPostSetting && ( game . queuehash ?. length || game . awsRegion ?. length ) ) ||
2608+ approvingAmbassadorIsApproved
2609+ ) {
25892610 adminBikeTag =
25902611 adminBikeTag ??
25912612 new BikeTagClient ( getBikeTagClientOpts ( { method : 'get' } as Request , true , true , game ) )
@@ -2608,7 +2629,11 @@ export const getActiveQueueForGame = async (
26082629 const diff = now - t . mysteryTime * 1000
26092630 const isTimedOut = diff > tagAutoPostTimer
26102631 if ( isTimedOut ) {
2611- log ( 'Tag timed out' , { tagnumber : t . tagnumber , mysteryTime : t . mysteryTime , diff } , 'info' )
2632+ log (
2633+ 'Tag timed out' ,
2634+ { tagnumber : t . tagnumber , mysteryTime : t . mysteryTime , diff } ,
2635+ 'info' ,
2636+ )
26122637 } else {
26132638 log ( 'Tag not timed out' , { now, mysteryTime : t . mysteryTime , diff } , 'info' )
26142639 }
@@ -2621,7 +2646,11 @@ export const getActiveQueueForGame = async (
26212646 }
26222647 }
26232648 } else {
2624- log ( 'Auto-post setting incomplete and no approving ambassador, skipping queue processing' , { game : game . name } , 'error' )
2649+ log (
2650+ 'Auto-post setting incomplete and no approving ambassador, skipping queue processing' ,
2651+ { game : game . name } ,
2652+ 'error' ,
2653+ )
26252654 }
26262655
26272656 return { queuedTags, completedTags, timedOutTags }
@@ -2803,7 +2832,8 @@ export const handleAuth0ProfileRequest = async (req: Request, profile: any): Pro
28032832 if ( typeof response . data === 'string' ) {
28042833 body = response . data
28052834 } else if ( Array . isArray ( response . data ) ) {
2806- if ( response . data ?. length ) log ( 'well how did this happen?' , { 'response.data' : response . data } , 'warn' )
2835+ if ( response . data ?. length )
2836+ log ( 'well how did this happen?' , { 'response.data' : response . data } , 'warn' )
28072837 body = ''
28082838 } else {
28092839 const profileDataResponse = profile . isBikeTagAmbassador
@@ -2953,7 +2983,7 @@ export const sendBikeTagPostNotificationToBlueSky = async (
29532983 const bskyPass = process . env . BSKY_PASS
29542984 const bskyServer = process . env . BSKY_SERVER ?? 'https://bsky.social'
29552985
2956- log ( 'sending bluesky on behalf of ' + bskyUser , { winningTagnumber, bskyUser} )
2986+ log ( 'sending bluesky on behalf of ' + bskyUser , { winningTagnumber, bskyUser } )
29572987
29582988 const agent = new AtpAgent ( {
29592989 service : bskyServer ,
@@ -3405,7 +3435,9 @@ export const launchGameTag = async (
34053435
34063436 if ( ! launchTag . mysteryImageUrl ?. length ) {
34073437 return {
3408- results : [ { message : 'Mystery image is required to launch the game' , error : 'missing image' } ] ,
3438+ results : [
3439+ { message : 'Mystery image is required to launch the game' , error : 'missing image' } ,
3440+ ] ,
34093441 errors : true ,
34103442 }
34113443 }
0 commit comments