// src/pages/api/upload.ts import type { APIRoute } from 'astro'; import { parse, stringify, transform } from 'csv'; import { Readable } from 'stream'; import { client } from '../../db/typesense'; import chalk from 'chalk'; import { db, ClosePool } from '../../db/index'; // Define the transformation logic const transformer = transform({ parallel: 1 }, async function(this: any, row: any, callback: any) { try { // Specific query bsaed on tcgcollector CSV const query = String(Object.values(row)[1]); const setname = String(Object.values(row)[4]).replace(/Wizards of the coast promos/ig,'WoTC Promo'); const cardNumber = String(Object.values(row)[7]); console.log(`${query} ${cardNumber} : ${setname}`); // Use Typesense to find the card because we can easily use the combined fields let cards = await client.collections('cards').documents().search({ q: query, query_by: 'productName', filter_by: `setName:\`${setname}\` && number:${cardNumber}` }); if (cards.hits?.length === 0) { // Try without card number cards = await client.collections('cards').documents().search({ q: query, query_by: 'productName', filter_by: `setName:\`${setname}\`` }); } if (cards.hits?.length === 0) { // Try without set name cards = await client.collections('cards').documents().search({ q: query, query_by: 'productName', filter_by: `number:${cardNumber}` }); } if (cards.hits?.length === 0) { // I give up, just output the values from the csv console.log(chalk.red(' - not found')); const newRow = { ...row }; newRow.Variant = ''; newRow.marketPrice = ''; this.push(newRow); } else { for (const card of cards.hits?.map((hit: any) => hit.document) ?? []) { console.log(chalk.blue(` - ${card.cardId} : ${card.productName} : ${card.number}`), chalk.yellow(`${card.setName}`), chalk.green(`${card.variant}`)); const variant = await db.query.cards.findFirst({ with: { prices: true, tcgdata: true }, where: { cardId: card.cardId } }); const newRow = { ...row }; newRow.Variant = variant?.variant; newRow.marketPrice = variant?.prices.find(p => p.condition === 'Near Mint')?.marketPrice; this.push(newRow); } } callback(); } catch (error) { callback(error); } }); export const POST: APIRoute = async ({ request }) => { try { const formData = await request.formData(); const file = formData.get('file') as File; const inputStream = Readable.from(file.stream()); if (!file) { return new Response('No file uploaded', { status: 400 }); } // Pipe the streams: Read -> Parse -> Transform -> Stringify -> Write const outputStream = inputStream .on('error', (error) => console.error('Input stream error:', error)) .pipe(parse({ columns: true, trim: true })) .on('error', (error) => console.error('Parse error:', error)) .pipe(transformer) .on('error', (error) => console.error('Transform error:', error)) .pipe(stringify({ header: true })) .on('error', (error) => console.error('Stringify error:', error)); // outputStream.on('finish', () => { // ClosePool(); // }).on('error', (error) => { // ClosePool(); // }); return new Response(outputStream as any, { headers: { 'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename=transformed.csv', }, }); } catch (error) { console.error('Error processing CSV stream:', error); return new Response('Internal Server Error', { status: 500 }); } };