Fix Node.js 18 compatibility for Vercel deployment
- Switch from @neondatabase/serverless to standard 'pg' client for Node.js 18 support - Update Node.js version to 18.x as required by Vercel - Replace Neon template literals with standard parameterized queries - Add proper connection pooling and SSL configuration for production - All API endpoints updated: /api/v1/cards/, /api/v1/cards/[id], /api/migrate - Build tested and working successfully
This commit is contained in:
parent
bdaa257c88
commit
6f40a5058d
5 changed files with 236 additions and 155 deletions
|
|
@ -1,9 +1,12 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { neon } from '@neondatabase/serverless';
|
||||
import { Pool } from 'pg';
|
||||
import cardsData from '../src/data/cards.json';
|
||||
|
||||
// Initialize Neon client
|
||||
const sql = neon(process.env.DATABASE_URL!);
|
||||
// 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;
|
||||
|
|
@ -49,12 +52,14 @@ export default async function handler(req: NextRequest) {
|
|||
});
|
||||
}
|
||||
|
||||
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 sql`SELECT COUNT(*) as count FROM cards`;
|
||||
const cardCount = existingCards[0]?.count || 0;
|
||||
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({
|
||||
|
|
@ -75,7 +80,7 @@ export default async function handler(req: NextRequest) {
|
|||
|
||||
for (const card of cards) {
|
||||
try {
|
||||
await sql`
|
||||
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,
|
||||
|
|
@ -83,39 +88,42 @@ export default async function handler(req: NextRequest) {
|
|||
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 (
|
||||
${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()}
|
||||
$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}`;
|
||||
|
|
@ -125,8 +133,8 @@ export default async function handler(req: NextRequest) {
|
|||
}
|
||||
|
||||
// Get final count to verify
|
||||
const finalCount = await sql`SELECT COUNT(*) as count FROM cards`;
|
||||
const finalCardCount = finalCount[0]?.count || 0;
|
||||
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!`);
|
||||
|
||||
|
|
@ -151,5 +159,7 @@ export default async function handler(req: NextRequest) {
|
|||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { neon } from '@neondatabase/serverless'
|
||||
import { Pool } from 'pg'
|
||||
|
||||
// Initialize Neon client
|
||||
const sql = neon(process.env.DATABASE_URL!)
|
||||
// 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
|
||||
|
|
@ -70,34 +73,39 @@ export default async function handler(req: NextRequest) {
|
|||
}
|
||||
|
||||
// Query the database for the specific card
|
||||
const result = await sql`SELECT * FROM cards WHERE id = ${parseInt(cardId)}`
|
||||
const client = await pool.connect()
|
||||
try {
|
||||
const result = await client.query('SELECT * FROM cards WHERE id = $1', [parseInt(cardId)])
|
||||
|
||||
if (result.length === 0) {
|
||||
return new NextResponse(JSON.stringify({ error: 'Card not found' }), {
|
||||
status: 404,
|
||||
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()
|
||||
}
|
||||
|
||||
// Transform the result to match expected format
|
||||
const card = {
|
||||
...result[0],
|
||||
colors: result[0].colors ? (typeof result[0].colors === 'string' ? JSON.parse(result[0].colors) : result[0].colors) : null,
|
||||
artwork_crop_coords: result[0].artwork_crop_coords ?
|
||||
(typeof result[0].artwork_crop_coords === 'string' ? JSON.parse(result[0].artwork_crop_coords) : result[0].artwork_crop_coords) : null
|
||||
}
|
||||
|
||||
return new NextResponse(JSON.stringify(card), {
|
||||
status: 200,
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('API Error:', error)
|
||||
return new NextResponse(JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { neon } from '@neondatabase/serverless'
|
||||
import { Pool } from 'pg'
|
||||
|
||||
// Initialize Neon client
|
||||
const sql = neon(process.env.DATABASE_URL!)
|
||||
// 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
|
||||
|
|
@ -54,57 +57,65 @@ export default async function handler(req: NextRequest) {
|
|||
const game = searchParams.get('game')
|
||||
const set_name = searchParams.get('set_name')
|
||||
|
||||
// Build the query dynamically using template literals
|
||||
// Build the query dynamically
|
||||
let baseQuery = 'SELECT * FROM cards WHERE 1=1'
|
||||
let queryConditions: string[] = []
|
||||
let queryValues: any[] = []
|
||||
let queryParams: any[] = []
|
||||
let paramIndex = 1
|
||||
|
||||
if (game) {
|
||||
queryConditions.push(`game = '${game.replace(/'/g, "''")}'`)
|
||||
baseQuery += ` AND game = $${paramIndex}`
|
||||
queryParams.push(game)
|
||||
paramIndex++
|
||||
}
|
||||
|
||||
if (set_name) {
|
||||
queryConditions.push(`set_name = '${set_name.replace(/'/g, "''")}'`)
|
||||
baseQuery += ` AND set_name = $${paramIndex}`
|
||||
queryParams.push(set_name)
|
||||
paramIndex++
|
||||
}
|
||||
|
||||
if (search) {
|
||||
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 ')
|
||||
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 ${limit}`
|
||||
baseQuery += ` LIMIT $${paramIndex}`
|
||||
queryParams.push(limit)
|
||||
paramIndex++
|
||||
}
|
||||
|
||||
if (skip > 0) {
|
||||
baseQuery += ` OFFSET ${skip}`
|
||||
baseQuery += ` OFFSET $${paramIndex}`
|
||||
queryParams.push(skip)
|
||||
}
|
||||
|
||||
// Execute the query using unsafe (for dynamic queries)
|
||||
const result = await sql.unsafe(baseQuery) as unknown as any[]
|
||||
// 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.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
|
||||
}))
|
||||
// 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',
|
||||
},
|
||||
})
|
||||
return new NextResponse(JSON.stringify(cards), {
|
||||
status: 200,
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
client.release()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('API Error:', error)
|
||||
|
|
|
|||
163
package-lock.json
generated
163
package-lock.json
generated
|
|
@ -8,7 +8,6 @@
|
|||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@neondatabase/serverless": "^0.10.4",
|
||||
"@stackframe/stack": "^2.8.22",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
|
|
@ -17,6 +16,7 @@
|
|||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.126",
|
||||
"@types/pg": "^8.15.4",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
"@vercel/blob": "^1.1.1",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"axios": "^1.10.0",
|
||||
"pg": "^8.16.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.7.0",
|
||||
|
|
@ -3903,15 +3904,6 @@
|
|||
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@neondatabase/serverless": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.10.4.tgz",
|
||||
"integrity": "sha512-2nZuh3VUO9voBauuh+IGYRhGU/MskWHt1IuZvHcJw6GLjDgtqj/KViKo7SIrLdGLdot7vFbiRRw+BgEy3wT9HA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/pg": "8.11.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.2.tgz",
|
||||
|
|
@ -6660,14 +6652,14 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/pg": {
|
||||
"version": "8.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz",
|
||||
"integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==",
|
||||
"version": "8.15.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz",
|
||||
"integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"pg-protocol": "*",
|
||||
"pg-types": "^4.0.1"
|
||||
"pg-types": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prettier": {
|
||||
|
|
@ -15685,6 +15677,46 @@
|
|||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.16.3",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.9.1",
|
||||
"pg-pool": "^3.10.1",
|
||||
"pg-protocol": "^1.10.3",
|
||||
"pg-types": "2.2.0",
|
||||
"pgpass": "1.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"pg-cloudflare": "^1.2.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pg-native": ">=3.0.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"pg-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pg-cloudflare": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
|
||||
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pg-connection-string": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
|
||||
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-int8": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||
|
|
@ -15694,13 +15726,13 @@
|
|||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-numeric": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz",
|
||||
"integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"node_modules/pg-pool": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
|
||||
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"pg": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-protocol": {
|
||||
|
|
@ -15710,21 +15742,28 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz",
|
||||
"integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"pg-numeric": "1.0.2",
|
||||
"postgres-array": "~3.0.1",
|
||||
"postgres-bytea": "~3.0.0",
|
||||
"postgres-date": "~2.1.0",
|
||||
"postgres-interval": "^3.0.0",
|
||||
"postgres-range": "^1.1.1"
|
||||
"postgres-array": "~2.0.0",
|
||||
"postgres-bytea": "~1.0.0",
|
||||
"postgres-date": "~1.0.4",
|
||||
"postgres-interval": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pgpass": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"split2": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
|
|
@ -17161,50 +17200,44 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
|
||||
"integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-bytea": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
|
||||
"integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"obuf": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-date": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
|
||||
"integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-interval": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
|
||||
"integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-range": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz",
|
||||
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
|
@ -19193,6 +19226,15 @@
|
|||
"wbuf": "^1.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
|
@ -21737,6 +21779,15 @@
|
|||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "20.x"
|
||||
"node": "18.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@neondatabase/serverless": "^0.10.4",
|
||||
"@stackframe/stack": "^2.8.22",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
|
|
@ -15,6 +14,7 @@
|
|||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.126",
|
||||
"@types/pg": "^8.15.4",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
"@vercel/blob": "^1.1.1",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"axios": "^1.10.0",
|
||||
"pg": "^8.16.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.7.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue