Skip to content

Commit dfef29a

Browse files
authored
Merge pull request #314 from KenEucker/develop
updating to biketag@3.5.46
2 parents 4d8ec8c + 7edec22 commit dfef29a

12 files changed

Lines changed: 537 additions & 471 deletions

File tree

functions/common/methods.ts

Lines changed: 106 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -66,30 +66,27 @@ export const getGameSiteUrl = (gameName = ''): string => {
6666
}
6767

6868
export 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 =
594591
const 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+
597600
const 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+
605621
const 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

924936
const 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

12241227
const 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) => {
12701277
export 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 (/^queue\//.test(key) && !key.endsWith('/index.json') && !isQueueSizedVariantKey(key)) {
1295+
if (
1296+
/^queue\//.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
}

functions/queue-fix.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
*
3434
* - non-webp: file in queue/ is not .webp (or unparsed name).
3535
* - missing-variants: primary .webp exists but _medium/_small siblings missing in queue/.
36-
* - wrong-round: filename round ≠ expected (found=current, mystery=current+1). Deletable.
36+
* - wrong-round: filename round ≠ expected (found=current or current+1, mystery=current+1). Deletable.
3737
* - duplicate-uploader: same player has files spanning rounds without a normal found+mystery pair.
3838
* - orphaned-main-found: past-round found still in queue/, main/ missing that round's --found.
3939
* Side-by-side preview uses main/ --mystery file for comparison. Move to main if repairable.

0 commit comments

Comments
 (0)