diff --git a/.gitignore b/.gitignore
index a32ceec..41b7e2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,9 @@ pnpm-debug.log*
# imges from tcgplayer
public/cards/*
+# static assets
+/static/
+
# anything test
test.*
diff --git a/scripts/preload-tcgplayer.ts b/scripts/preload-tcgplayer.ts
index 1ddddcd..ef9931f 100644
--- a/scripts/preload-tcgplayer.ts
+++ b/scripts/preload-tcgplayer.ts
@@ -280,6 +280,6 @@ else {
await helper.UpdateVariants(db);
// index the card updates
-helper.upsertCardCollection(db);
+await helper.upsertCardCollection(db);
await ClosePool();
diff --git a/src/pages/api/upload.ts b/src/pages/api/upload.ts
new file mode 100644
index 0000000..7062147
--- /dev/null
+++ b/src/pages/api/upload.ts
@@ -0,0 +1,95 @@
+// 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 });
+ }
+};
diff --git a/src/pages/myprices.astro b/src/pages/myprices.astro
new file mode 100644
index 0000000..cf80082
--- /dev/null
+++ b/src/pages/myprices.astro
@@ -0,0 +1,26 @@
+---
+import Layout from '../layouts/Main.astro';
+import NavItems from '../components/NavItems.astro';
+import NavBar from '../components/NavBar.astro';
+import Footer from '../components/Footer.astro';
+
+---
+ (working title)Rigid's App Thing
+
diff --git a/src/pages/partials/cards.astro b/src/pages/partials/cards.astro
index cec76f3..458d9ed 100644
--- a/src/pages/partials/cards.astro
+++ b/src/pages/partials/cards.astro
@@ -287,7 +287,7 @@ const facets = searchResults.results.slice(1).map((result: any) => {
+/–
+