2025-07-21 14:53:06 -04:00
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
2025-07-21 16:46:58 -04:00
|
|
|
import { neon } from '@neondatabase/serverless'
|
|
|
|
|
|
|
|
|
|
// Initialize Neon client
|
|
|
|
|
const sql = neon(process.env.DATABASE_URL!)
|
2025-07-21 14:53:06 -04:00
|
|
|
|
|
|
|
|
interface Card {
|
|
|
|
|
id: number
|
|
|
|
|
name: string
|
|
|
|
|
set_name?: string
|
|
|
|
|
set_code?: string
|
|
|
|
|
card_number?: string
|
|
|
|
|
rarity?: string
|
|
|
|
|
game: string
|
|
|
|
|
mana_cost?: string
|
|
|
|
|
cmc?: number
|
|
|
|
|
card_type?: string
|
|
|
|
|
colors?: string[]
|
|
|
|
|
oracle_text?: string
|
|
|
|
|
flavor_text?: string
|
|
|
|
|
power?: string
|
|
|
|
|
toughness?: string
|
|
|
|
|
artist?: string
|
|
|
|
|
image_url?: string
|
|
|
|
|
stock_image_url?: string
|
|
|
|
|
artwork_crop_coords?: any
|
|
|
|
|
current_price?: number
|
|
|
|
|
market_price?: number
|
|
|
|
|
verified: boolean
|
|
|
|
|
created_at?: string
|
|
|
|
|
updated_at?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default async function handler(req: NextRequest) {
|
|
|
|
|
// Enable CORS
|
|
|
|
|
const headers = {
|
|
|
|
|
'Access-Control-Allow-Origin': '*',
|
|
|
|
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
|
|
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
|
|
|
return new NextResponse(null, { status: 200, headers })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(req.url)
|
|
|
|
|
const searchParams = url.searchParams
|
|
|
|
|
|
|
|
|
|
// Get query parameters
|
|
|
|
|
const skip = parseInt(searchParams.get('skip') || '0')
|
|
|
|
|
const limit = parseInt(searchParams.get('limit') || '100')
|
|
|
|
|
const search = searchParams.get('search')
|
|
|
|
|
const game = searchParams.get('game')
|
|
|
|
|
const set_name = searchParams.get('set_name')
|
|
|
|
|
|
2025-07-21 16:46:58 -04:00
|
|
|
// Build the query dynamically using template literals
|
|
|
|
|
let baseQuery = 'SELECT * FROM cards WHERE 1=1'
|
|
|
|
|
let queryConditions: string[] = []
|
|
|
|
|
let queryValues: any[] = []
|
2025-07-21 14:53:06 -04:00
|
|
|
|
|
|
|
|
if (game) {
|
2025-07-21 16:46:58 -04:00
|
|
|
queryConditions.push(`game = '${game.replace(/'/g, "''")}'`)
|
2025-07-21 14:53:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (set_name) {
|
2025-07-21 16:46:58 -04:00
|
|
|
queryConditions.push(`set_name = '${set_name.replace(/'/g, "''")}'`)
|
2025-07-21 14:53:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (search) {
|
2025-07-21 16:46:58 -04:00
|
|
|
const escapedSearch = search.replace(/'/g, "''")
|
|
|
|
|
queryConditions.push(`(name ILIKE '%${escapedSearch}%' OR oracle_text ILIKE '%${escapedSearch}%' OR card_type ILIKE '%${escapedSearch}%')`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Combine conditions
|
|
|
|
|
if (queryConditions.length > 0) {
|
|
|
|
|
baseQuery += ' AND ' + queryConditions.join(' AND ')
|
2025-07-21 14:53:06 -04:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 16:46:58 -04:00
|
|
|
baseQuery += ' ORDER BY name'
|
|
|
|
|
|
|
|
|
|
if (limit > 0) {
|
|
|
|
|
baseQuery += ` LIMIT ${limit}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (skip > 0) {
|
|
|
|
|
baseQuery += ` OFFSET ${skip}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute the query using unsafe (for dynamic queries)
|
|
|
|
|
const result = await sql.unsafe(baseQuery) as unknown as any[]
|
|
|
|
|
|
|
|
|
|
// Transform the result to match expected format
|
|
|
|
|
const cards = result.map((row: any) => ({
|
|
|
|
|
...row,
|
|
|
|
|
colors: row.colors ? (typeof row.colors === 'string' ? JSON.parse(row.colors) : row.colors) : null,
|
|
|
|
|
artwork_crop_coords: row.artwork_crop_coords ?
|
|
|
|
|
(typeof row.artwork_crop_coords === 'string' ? JSON.parse(row.artwork_crop_coords) : row.artwork_crop_coords) : null
|
|
|
|
|
}))
|
2025-07-21 14:53:06 -04:00
|
|
|
|
2025-07-21 16:46:58 -04:00
|
|
|
return new NextResponse(JSON.stringify(cards), {
|
2025-07-21 14:53:06 -04:00
|
|
|
status: 200,
|
|
|
|
|
headers: {
|
|
|
|
|
...headers,
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('API Error:', error)
|
2025-07-21 16:46:58 -04:00
|
|
|
return new NextResponse(JSON.stringify({
|
|
|
|
|
error: 'Internal server error',
|
|
|
|
|
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
|
|
|
}), {
|
2025-07-21 14:53:06 -04:00
|
|
|
status: 500,
|
|
|
|
|
headers: {
|
|
|
|
|
...headers,
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|