Fix Vercel function limit and eslint warnings
🔧 Vercel Function Limit Fix: - Remove unnecessary API functions to get under 12 function limit - Delete api/simple.js (test function) - Delete api/migrate.ts (old migration) - Delete api/admin/promote-user.js (one-time function) - Delete api/admin/update-preferences-schema.js (migration) - Delete api/v1/ directory (old API versions) - Now at 8 functions (well under 12 limit) 🚨 ESLint Warning Fixes: - Fix unnecessary escape characters in regex patterns - Replace [:\-] with [:−] in aiOcr.ts regex patterns ✅ Build should now succeed on Vercel Hobby plan
This commit is contained in:
parent
7463551065
commit
f441d1aee5
8 changed files with 13 additions and 642 deletions
|
|
@ -1,130 +0,0 @@
|
||||||
const { Pool } = require('pg');
|
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
|
||||||
if (req.method !== 'POST') {
|
|
||||||
return res.status(405).json({ error: 'Method not allowed' });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('Request body type:', typeof req.body);
|
|
||||||
console.log('Request body:', req.body);
|
|
||||||
|
|
||||||
const { username, userId } = req.body;
|
|
||||||
|
|
||||||
if (!username && !userId) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: 'Either username or userId is required'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = await pool.connect();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Find the user
|
|
||||||
let userQuery;
|
|
||||||
let userParams;
|
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
userQuery = 'SELECT id, username, email FROM users WHERE id = $1';
|
|
||||||
userParams = [userId];
|
|
||||||
} else {
|
|
||||||
userQuery = 'SELECT id, username, email FROM users WHERE username = $1';
|
|
||||||
userParams = [username];
|
|
||||||
}
|
|
||||||
|
|
||||||
const userResult = await client.query(userQuery, userParams);
|
|
||||||
|
|
||||||
if (userResult.rows.length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
error: 'User not found'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = userResult.rows[0];
|
|
||||||
|
|
||||||
// Check if user is already an admin
|
|
||||||
const adminCheckQuery = `
|
|
||||||
SELECT ur.user_id
|
|
||||||
FROM user_roles ur
|
|
||||||
JOIN roles r ON ur.role_id = r.id
|
|
||||||
WHERE ur.user_id = $1 AND r.name = 'admin'
|
|
||||||
`;
|
|
||||||
|
|
||||||
const adminCheck = await client.query(adminCheckQuery, [user.id]);
|
|
||||||
|
|
||||||
if (adminCheck.rows.length > 0) {
|
|
||||||
return res.status(200).json({
|
|
||||||
success: true,
|
|
||||||
message: 'User is already an admin',
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get admin role ID
|
|
||||||
const roleResult = await client.query(
|
|
||||||
'SELECT id FROM roles WHERE name = $1',
|
|
||||||
['admin']
|
|
||||||
);
|
|
||||||
|
|
||||||
if (roleResult.rows.length === 0) {
|
|
||||||
return res.status(500).json({
|
|
||||||
error: 'Admin role not found in database'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminRoleId = roleResult.rows[0].id;
|
|
||||||
|
|
||||||
// Add user to admin role
|
|
||||||
await client.query(
|
|
||||||
'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)',
|
|
||||||
[user.id, adminRoleId]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get updated user info with roles
|
|
||||||
const updatedUserQuery = `
|
|
||||||
SELECT
|
|
||||||
u.id, u.username, u.email, u.first_name, u.last_name,
|
|
||||||
ARRAY_AGG(DISTINCT r.name) as roles
|
|
||||||
FROM users u
|
|
||||||
LEFT JOIN user_roles ur ON u.id = ur.user_id
|
|
||||||
LEFT JOIN roles r ON ur.role_id = r.id
|
|
||||||
WHERE u.id = $1
|
|
||||||
GROUP BY u.id, u.username, u.email, u.first_name, u.last_name
|
|
||||||
`;
|
|
||||||
|
|
||||||
const updatedUser = await client.query(updatedUserQuery, [user.id]);
|
|
||||||
|
|
||||||
res.status(200).json({
|
|
||||||
success: true,
|
|
||||||
message: 'User successfully promoted to admin',
|
|
||||||
user: {
|
|
||||||
id: updatedUser.rows[0].id,
|
|
||||||
username: updatedUser.rows[0].username,
|
|
||||||
email: updatedUser.rows[0].email,
|
|
||||||
firstName: updatedUser.rows[0].first_name,
|
|
||||||
lastName: updatedUser.rows[0].last_name,
|
|
||||||
roles: updatedUser.rows[0].roles || []
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Promote user error:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: 'Internal server error',
|
|
||||||
details: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
const { Pool } = require('pg');
|
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
|
||||||
if (req.method !== 'POST') {
|
|
||||||
return res.status(405).json({ error: 'Method not allowed' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = await pool.connect();
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('Updating user_preferences schema to add OCR settings...');
|
|
||||||
|
|
||||||
// Check if ocr_settings column already exists
|
|
||||||
const columnCheck = await client.query(`
|
|
||||||
SELECT EXISTS (
|
|
||||||
SELECT FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'public'
|
|
||||||
AND table_name = 'user_preferences'
|
|
||||||
AND column_name = 'ocr_settings'
|
|
||||||
);
|
|
||||||
`);
|
|
||||||
|
|
||||||
if (columnCheck.rows[0].exists) {
|
|
||||||
return res.status(200).json({
|
|
||||||
success: true,
|
|
||||||
message: 'OCR settings column already exists',
|
|
||||||
already_updated: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add ocr_settings column
|
|
||||||
await client.query(`
|
|
||||||
ALTER TABLE user_preferences
|
|
||||||
ADD COLUMN ocr_settings JSONB DEFAULT '{
|
|
||||||
"preferred_service": "openai",
|
|
||||||
"openai_api_key": "",
|
|
||||||
"ollama_url": "http://localhost:11434",
|
|
||||||
"auto_add_to_collection": false,
|
|
||||||
"confidence_threshold": 80
|
|
||||||
}'::jsonb;
|
|
||||||
`);
|
|
||||||
|
|
||||||
console.log('✅ OCR settings column added to user_preferences table');
|
|
||||||
|
|
||||||
res.status(200).json({
|
|
||||||
success: true,
|
|
||||||
message: 'User preferences schema updated successfully with OCR settings'
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Schema update failed:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Schema update failed',
|
|
||||||
details: error.message
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
167
api/migrate.ts
167
api/migrate.ts
|
|
@ -1,167 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
import { Pool } from 'pg';
|
|
||||||
|
|
||||||
// Use require for JSON import to avoid module issues
|
|
||||||
const cardsData = require('../src/data/cards.json');
|
|
||||||
|
|
||||||
// Create PostgreSQL connection pool
|
|
||||||
const pool = new Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
interface JsonCard {
|
|
||||||
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[] | null;
|
|
||||||
oracle_text?: string;
|
|
||||||
flavor_text?: string | null;
|
|
||||||
power?: string | null;
|
|
||||||
toughness?: string | null;
|
|
||||||
loyalty?: string | null;
|
|
||||||
artist?: string | null;
|
|
||||||
image_url?: string;
|
|
||||||
stock_image_url?: string;
|
|
||||||
artwork_crop_coords?: any;
|
|
||||||
current_price?: number;
|
|
||||||
market_price?: number;
|
|
||||||
low_price?: number | null;
|
|
||||||
high_price?: number | null;
|
|
||||||
price_last_updated?: string | null;
|
|
||||||
ocr_confidence?: number | null;
|
|
||||||
ocr_raw_text?: string | null;
|
|
||||||
scryfall_id?: string | null;
|
|
||||||
tcg_player_id?: string | null;
|
|
||||||
verified: number; // JSON has number, but we want boolean
|
|
||||||
created_at?: string;
|
|
||||||
updated_at?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function handler(req: NextRequest) {
|
|
||||||
// Only allow POST requests for security
|
|
||||||
if (req.method !== 'POST') {
|
|
||||||
return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), {
|
|
||||||
status: 405,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = await pool.connect();
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('Starting card migration to Neon database...');
|
|
||||||
|
|
||||||
// Check if cards already exist to prevent duplicate migration
|
|
||||||
const existingCards = await client.query('SELECT COUNT(*) as count FROM cards');
|
|
||||||
const cardCount = existingCards.rows[0]?.count || 0;
|
|
||||||
|
|
||||||
if (cardCount > 0) {
|
|
||||||
return new NextResponse(JSON.stringify({
|
|
||||||
message: `Database already contains ${cardCount} cards. Migration skipped.`,
|
|
||||||
cards_count: cardCount
|
|
||||||
}), {
|
|
||||||
status: 200,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate cards from JSON
|
|
||||||
const cards = cardsData as JsonCard[];
|
|
||||||
console.log(`Migrating ${cards.length} cards...`);
|
|
||||||
|
|
||||||
let migratedCount = 0;
|
|
||||||
const errors: string[] = [];
|
|
||||||
|
|
||||||
for (const card of cards) {
|
|
||||||
try {
|
|
||||||
await client.query(`
|
|
||||||
INSERT INTO cards (
|
|
||||||
name, set_name, set_code, card_number, rarity, game, mana_cost, cmc,
|
|
||||||
card_type, colors, oracle_text, flavor_text, power, toughness, loyalty,
|
|
||||||
artist, image_url, stock_image_url, artwork_crop_coords, current_price,
|
|
||||||
market_price, low_price, high_price, price_last_updated, ocr_confidence,
|
|
||||||
ocr_raw_text, scryfall_id, tcg_player_id, verified, created_at, updated_at
|
|
||||||
) VALUES (
|
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15,
|
|
||||||
$16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31
|
|
||||||
)
|
|
||||||
`, [
|
|
||||||
card.name,
|
|
||||||
card.set_name || null,
|
|
||||||
card.set_code || null,
|
|
||||||
card.card_number || null,
|
|
||||||
card.rarity || null,
|
|
||||||
card.game,
|
|
||||||
card.mana_cost || null,
|
|
||||||
card.cmc || null,
|
|
||||||
card.card_type || null,
|
|
||||||
card.colors ? JSON.stringify(card.colors) : null,
|
|
||||||
card.oracle_text || null,
|
|
||||||
card.flavor_text || null,
|
|
||||||
card.power || null,
|
|
||||||
card.toughness || null,
|
|
||||||
card.loyalty || null,
|
|
||||||
card.artist || null,
|
|
||||||
card.image_url || null,
|
|
||||||
card.stock_image_url || null,
|
|
||||||
card.artwork_crop_coords ? JSON.stringify(card.artwork_crop_coords) : null,
|
|
||||||
card.current_price || null,
|
|
||||||
card.market_price || null,
|
|
||||||
card.low_price || null,
|
|
||||||
card.high_price || null,
|
|
||||||
card.price_last_updated ? new Date(card.price_last_updated) : null,
|
|
||||||
card.ocr_confidence || null,
|
|
||||||
card.ocr_raw_text || null,
|
|
||||||
card.scryfall_id || null,
|
|
||||||
card.tcg_player_id || null,
|
|
||||||
Boolean(card.verified),
|
|
||||||
card.created_at ? new Date(card.created_at) : new Date(),
|
|
||||||
card.updated_at ? new Date(card.updated_at) : new Date()
|
|
||||||
]);
|
|
||||||
migratedCount++;
|
|
||||||
} catch (error) {
|
|
||||||
const errorMsg = `Error migrating card ${card.name}: ${(error as Error).message}`;
|
|
||||||
console.error(errorMsg);
|
|
||||||
errors.push(errorMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get final count to verify
|
|
||||||
const finalCount = await client.query('SELECT COUNT(*) as count FROM cards');
|
|
||||||
const finalCardCount = finalCount.rows[0]?.count || 0;
|
|
||||||
|
|
||||||
console.log(`✅ Successfully migrated ${migratedCount} cards to Neon database!`);
|
|
||||||
|
|
||||||
return new NextResponse(JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
message: `Successfully migrated ${migratedCount} out of ${cards.length} cards`,
|
|
||||||
cards_migrated: migratedCount,
|
|
||||||
total_cards_in_db: finalCardCount,
|
|
||||||
errors: errors.length > 0 ? errors : undefined
|
|
||||||
}), {
|
|
||||||
status: 200,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Migration failed:', error);
|
|
||||||
return new NextResponse(JSON.stringify({
|
|
||||||
success: false,
|
|
||||||
error: 'Migration failed',
|
|
||||||
details: (error as Error).message
|
|
||||||
}), {
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
export default function handler(req, res) {
|
|
||||||
res.status(200).json({
|
|
||||||
message: 'Simple JS endpoint works!',
|
|
||||||
method: req.method,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
environment: process.env.NODE_ENV || 'unknown',
|
|
||||||
has_database_url: !!process.env.DATABASE_URL,
|
|
||||||
has_jwt_secret: !!process.env.JWT_SECRET,
|
|
||||||
database_url_preview: process.env.DATABASE_URL ?
|
|
||||||
process.env.DATABASE_URL.substring(0, 30) + '...' : 'not set'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { Pool } from 'pg'
|
|
||||||
|
|
||||||
// Create PostgreSQL connection pool
|
|
||||||
const pool = new Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
|
||||||
})
|
|
||||||
|
|
||||||
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, OPTIONS',
|
|
||||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method === 'OPTIONS') {
|
|
||||||
return new NextResponse(null, { status: 200, headers })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method !== 'GET') {
|
|
||||||
return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), {
|
|
||||||
status: 405,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Extract card ID from URL
|
|
||||||
const url = new URL(req.url)
|
|
||||||
const pathParts = url.pathname.split('/')
|
|
||||||
const cardId = pathParts[pathParts.length - 1]
|
|
||||||
|
|
||||||
if (!cardId || isNaN(parseInt(cardId))) {
|
|
||||||
return new NextResponse(JSON.stringify({ error: 'Invalid card ID' }), {
|
|
||||||
status: 400,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query the database for the specific card
|
|
||||||
const client = await pool.connect()
|
|
||||||
try {
|
|
||||||
const result = await client.query('SELECT * FROM cards WHERE id = $1', [parseInt(cardId)])
|
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
|
||||||
return new NextResponse(JSON.stringify({ error: 'Card not found' }), {
|
|
||||||
status: 404,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform the result to match expected format
|
|
||||||
const card = {
|
|
||||||
...result.rows[0],
|
|
||||||
colors: result.rows[0].colors ? (typeof result.rows[0].colors === 'string' ? JSON.parse(result.rows[0].colors) : result.rows[0].colors) : null,
|
|
||||||
artwork_crop_coords: result.rows[0].artwork_crop_coords ?
|
|
||||||
(typeof result.rows[0].artwork_crop_coords === 'string' ? JSON.parse(result.rows[0].artwork_crop_coords) : result.rows[0].artwork_crop_coords) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NextResponse(JSON.stringify(card), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
client.release()
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('API Error:', error)
|
|
||||||
return new NextResponse(JSON.stringify({
|
|
||||||
error: 'Internal server error',
|
|
||||||
details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined
|
|
||||||
}), {
|
|
||||||
status: 500,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { Pool } from 'pg'
|
|
||||||
|
|
||||||
// Create PostgreSQL connection pool
|
|
||||||
const pool = new Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
|
||||||
})
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
// Build the query dynamically
|
|
||||||
let baseQuery = 'SELECT * FROM cards WHERE 1=1'
|
|
||||||
let queryParams: any[] = []
|
|
||||||
let paramIndex = 1
|
|
||||||
|
|
||||||
if (game) {
|
|
||||||
baseQuery += ` AND game = $${paramIndex}`
|
|
||||||
queryParams.push(game)
|
|
||||||
paramIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (set_name) {
|
|
||||||
baseQuery += ` AND set_name = $${paramIndex}`
|
|
||||||
queryParams.push(set_name)
|
|
||||||
paramIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
baseQuery += ` AND (name ILIKE $${paramIndex} OR oracle_text ILIKE $${paramIndex} OR card_type ILIKE $${paramIndex})`
|
|
||||||
queryParams.push(`%${search}%`)
|
|
||||||
paramIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
baseQuery += ' ORDER BY name'
|
|
||||||
|
|
||||||
if (limit > 0) {
|
|
||||||
baseQuery += ` LIMIT $${paramIndex}`
|
|
||||||
queryParams.push(limit)
|
|
||||||
paramIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skip > 0) {
|
|
||||||
baseQuery += ` OFFSET $${paramIndex}`
|
|
||||||
queryParams.push(skip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the query
|
|
||||||
const client = await pool.connect()
|
|
||||||
try {
|
|
||||||
const result = await client.query(baseQuery, queryParams)
|
|
||||||
|
|
||||||
// Transform the result to match expected format
|
|
||||||
const cards = result.rows.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
|
|
||||||
}))
|
|
||||||
|
|
||||||
return new NextResponse(JSON.stringify(cards), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
client.release()
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('API Error:', error)
|
|
||||||
return new NextResponse(JSON.stringify({
|
|
||||||
error: 'Internal server error',
|
|
||||||
details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined
|
|
||||||
}), {
|
|
||||||
status: 500,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -33,7 +33,7 @@ const Settings: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPreferences();
|
loadPreferences();
|
||||||
}, [user]);
|
}, [user]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const loadPreferences = async () => {
|
const loadPreferences = async () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
|
||||||
|
|
@ -159,18 +159,18 @@ class AICardOCR {
|
||||||
if (line.startsWith('```') || line.startsWith('#')) continue;
|
if (line.startsWith('```') || line.startsWith('#')) continue;
|
||||||
|
|
||||||
// Look for key-value patterns
|
// Look for key-value patterns
|
||||||
if (line.match(/card\s*name[:\-]?\s*(.+)/i)) {
|
if (line.match(/card\s*name[:−]?\s*(.+)/i)) {
|
||||||
cardName = line.replace(/card\s*name[:\-]?\s*/i, '').replace(/['"]/g, '');
|
cardName = line.replace(/card\s*name[:−]?\s*/i, '').replace(/['"]/g, '');
|
||||||
} else if (line.match(/name[:\-]?\s*(.+)/i) && !cardName) {
|
} else if (line.match(/name[:−]?\s*(.+)/i) && !cardName) {
|
||||||
cardName = line.replace(/name[:\-]?\s*/i, '').replace(/['"]/g, '');
|
cardName = line.replace(/name[:−]?\s*/i, '').replace(/['"]/g, '');
|
||||||
} else if (line.match(/set[:\-]?\s*(.+)/i)) {
|
} else if (line.match(/set[:−]?\s*(.+)/i)) {
|
||||||
setName = line.replace(/set[:\-]?\s*/i, '').replace(/['"]/g, '');
|
setName = line.replace(/set[:−]?\s*/i, '').replace(/['"]/g, '');
|
||||||
} else if (line.match(/hp[:\-]?\s*(\d+)/i)) {
|
} else if (line.match(/hp[:−]?\s*(\d+)/i)) {
|
||||||
hp = line.match(/hp[:\-]?\s*(\d+)/i)?.[1] || '';
|
hp = line.match(/hp[:−]?\s*(\d+)/i)?.[1] || '';
|
||||||
} else if (line.match(/type[:\-]?\s*(.+)/i)) {
|
} else if (line.match(/type[:−]?\s*(.+)/i)) {
|
||||||
cardType = line.replace(/type[:\-]?\s*/i, '').replace(/['"]/g, '');
|
cardType = line.replace(/type[:−]?\s*/i, '').replace(/['"]/g, '');
|
||||||
} else if (line.match(/rarity[:\-]?\s*(.+)/i)) {
|
} else if (line.match(/rarity[:−]?\s*(.+)/i)) {
|
||||||
rarity = line.replace(/rarity[:\-]?\s*/i, '').replace(/['"]/g, '');
|
rarity = line.replace(/rarity[:−]?\s*/i, '').replace(/['"]/g, '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue