Expand game query filters and update UI assets
- Backend: extend filters with dateFrom/dateTo, won, roundsMin/Max and colors; implement color identity using jsonb and order by date with limit/offset. - Frontend: adjust slideshow spacing in index.html and include new images; bump version to 2.0.5.
This commit is contained in:
@@ -97,43 +97,16 @@ export class GameRepository extends Repository {
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if (filters.won !== undefined) {
|
||||
query += ` AND g.won = $${paramCount}`
|
||||
params.push(filters.won)
|
||||
paramCount++
|
||||
}
|
||||
if (filters.won !== undefined) {
|
||||
query += ` AND g.won = $${paramCount}`
|
||||
params.push(filters.won)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if (filters.roundsMin !== undefined) {
|
||||
query += ` AND g.rounds >= $${paramCount}`
|
||||
params.push(filters.roundsMin)
|
||||
paramCount++
|
||||
}
|
||||
query += ` ORDER BY g.date DESC LIMIT $${paramCount} OFFSET $${paramCount + 1}`
|
||||
params.push(limit, offset)
|
||||
|
||||
if (filters.roundsMax !== undefined) {
|
||||
query += ` AND g.rounds <= $${paramCount}`
|
||||
params.push(filters.roundsMax)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if (filters.colors && filters.colors.length > 0) {
|
||||
// Filter by commander color identity - checks if colors array contains any of the specified colors
|
||||
const colorConditions = filters.colors.map(() => {
|
||||
const condition = `cmdr.colors @> $${paramCount}::jsonb`
|
||||
paramCount++
|
||||
return condition
|
||||
})
|
||||
query += ` AND (${colorConditions.join(' OR ')})`
|
||||
filters.colors.forEach(color => {
|
||||
params.push(JSON.stringify([color]))
|
||||
})
|
||||
paramCount -= filters.colors.length
|
||||
paramCount += filters.colors.length
|
||||
}
|
||||
|
||||
query += ` ORDER BY g.date DESC LIMIT $${paramCount} OFFSET $${paramCount + 1}`
|
||||
params.push(limit, offset)
|
||||
|
||||
return dbManager.all(query, params)
|
||||
return dbManager.all(query, params)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,48 +161,15 @@ export class GameRepository extends Repository {
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if (filters.dateTo) {
|
||||
query += ` AND g.date <= $${paramCount}`
|
||||
params.push(filters.dateTo)
|
||||
paramCount++
|
||||
}
|
||||
if (filters.dateTo) {
|
||||
query += ` AND g.date <= $${paramCount}`
|
||||
params.push(filters.dateTo)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if (filters.won !== undefined) {
|
||||
query += ` AND g.won = $${paramCount}`
|
||||
params.push(filters.won)
|
||||
paramCount++
|
||||
}
|
||||
query += ` ORDER BY g.date DESC`
|
||||
|
||||
if (filters.roundsMin !== undefined) {
|
||||
query += ` AND g.rounds >= $${paramCount}`
|
||||
params.push(filters.roundsMin)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if (filters.roundsMax !== undefined) {
|
||||
query += ` AND g.rounds <= $${paramCount}`
|
||||
params.push(filters.roundsMax)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if (filters.colors && filters.colors.length > 0) {
|
||||
// Filter by commander color identity
|
||||
const colorConditions = filters.colors.map(() => {
|
||||
const condition = `cmdr.colors @> $${paramCount}::jsonb`
|
||||
paramCount++
|
||||
return condition
|
||||
})
|
||||
query += ` AND (${colorConditions.join(' OR ')})`
|
||||
filters.colors.forEach(color => {
|
||||
params.push(JSON.stringify([color]))
|
||||
})
|
||||
paramCount -= filters.colors.length
|
||||
paramCount += filters.colors.length
|
||||
}
|
||||
|
||||
query += ` ORDER BY g.date DESC`
|
||||
|
||||
return dbManager.all(query, params)
|
||||
return dbManager.all(query, params)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,79 +100,25 @@ const updateGameSchema = z.object({
|
||||
})
|
||||
|
||||
const gameQuerySchema = z.object({
|
||||
q: z
|
||||
.string('Search query must be a string')
|
||||
.min(1, 'Search query cannot be empty')
|
||||
.max(50, 'Search query limited to 50 characters')
|
||||
.optional(),
|
||||
limit: z
|
||||
.coerce
|
||||
.number('Limit must be a number')
|
||||
.int('Limit must be a whole number')
|
||||
.min(1, 'Minimum 1 game per page')
|
||||
.max(100, 'Maximum 100 games per page')
|
||||
.default(50),
|
||||
offset: z
|
||||
.coerce
|
||||
.number('Offset must be a number')
|
||||
.int('Offset must be a whole number')
|
||||
.min(0, 'Offset cannot be negative')
|
||||
.default(0),
|
||||
// Date range filters
|
||||
dateFrom: z
|
||||
.string('Date from must be a string')
|
||||
.refine((date) => !isNaN(Date.parse(date)), {
|
||||
message: 'Invalid date format (use YYYY-MM-DD)'
|
||||
})
|
||||
.optional(),
|
||||
dateTo: z
|
||||
.string('Date to must be a string')
|
||||
.refine((date) => !isNaN(Date.parse(date)), {
|
||||
message: 'Invalid date format (use YYYY-MM-DD)'
|
||||
})
|
||||
.optional(),
|
||||
// Player count filter
|
||||
playerCount: z
|
||||
.coerce
|
||||
.number('Player count must be a number')
|
||||
.int('Player count must be a whole number')
|
||||
.min(2, 'Minimum 2 players')
|
||||
.max(8, 'Maximum 8 players')
|
||||
.optional(),
|
||||
// Commander ID filter
|
||||
commanderId: z
|
||||
.coerce
|
||||
.number('Commander ID must be a number')
|
||||
.int('Commander ID must be a whole number')
|
||||
.positive('Commander ID must be positive')
|
||||
.optional(),
|
||||
// Win/Loss filter
|
||||
won: z
|
||||
.enum(['true', 'false'])
|
||||
.transform(val => val === 'true')
|
||||
.optional(),
|
||||
// Rounds range filters
|
||||
roundsMin: z
|
||||
.coerce
|
||||
.number('Min rounds must be a number')
|
||||
.int('Min rounds must be a whole number')
|
||||
.min(1, 'Minimum 1 round')
|
||||
.optional(),
|
||||
roundsMax: z
|
||||
.coerce
|
||||
.number('Max rounds must be a number')
|
||||
.int('Max rounds must be a whole number')
|
||||
.max(50, 'Maximum 50 rounds')
|
||||
.optional(),
|
||||
// Color filters (comma-separated: W,U,B,R,G)
|
||||
colors: z
|
||||
.string('Colors must be a string')
|
||||
.transform(val => val.split(',').filter(c => c.trim()))
|
||||
.refine(colors => colors.every(c => /^[WUBRG]$/.test(c)), {
|
||||
message: 'Invalid color format (use W,U,B,R,G)'
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
q: z
|
||||
.string('Search query must be a string')
|
||||
.min(1, 'Search query cannot be empty')
|
||||
.max(50, 'Search query limited to 50 characters')
|
||||
.optional(),
|
||||
limit: z
|
||||
.coerce
|
||||
.number('Limit must be a number')
|
||||
.int('Limit must be a whole number')
|
||||
.min(1, 'Minimum 1 game per page')
|
||||
.max(100, 'Maximum 100 games per page')
|
||||
.default(50),
|
||||
offset: z
|
||||
.coerce
|
||||
.number('Offset must be a number')
|
||||
.int('Offset must be a whole number')
|
||||
.min(0, 'Offset cannot be negative')
|
||||
.default(0)
|
||||
})
|
||||
|
||||
export default async function gameRoutes(fastify, options) {
|
||||
// Initialize repositories
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Slideshow Section -->
|
||||
<div class="mt-12 max-w-6xl mx-auto">
|
||||
<!-- Preview Slideshow Section -->
|
||||
<div class="mt-12 max-w-5xl mx-auto">
|
||||
<div
|
||||
class="bg-white rounded-lg shadow-lg overflow-hidden"
|
||||
x-data="slideshow()"
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.0.5
|
||||
2.0.6
|
||||
|
||||
Reference in New Issue
Block a user