Merge branch 'master' into feat/inventory
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,6 +26,9 @@ pnpm-debug.log*
|
|||||||
# imges from tcgplayer
|
# imges from tcgplayer
|
||||||
public/cards/*
|
public/cards/*
|
||||||
|
|
||||||
|
# static assets
|
||||||
|
/static/
|
||||||
|
|
||||||
# anything test
|
# anything test
|
||||||
test.*
|
test.*
|
||||||
|
|
||||||
|
|||||||
@@ -280,6 +280,6 @@ else {
|
|||||||
await helper.UpdateVariants(db);
|
await helper.UpdateVariants(db);
|
||||||
|
|
||||||
// index the card updates
|
// index the card updates
|
||||||
helper.upsertCardCollection(db);
|
await helper.upsertCardCollection(db);
|
||||||
|
|
||||||
await ClosePool();
|
await ClosePool();
|
||||||
|
|||||||
95
src/pages/api/upload.ts
Normal file
95
src/pages/api/upload.ts
Normal file
@@ -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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
26
src/pages/myprices.astro
Normal file
26
src/pages/myprices.astro
Normal file
@@ -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';
|
||||||
|
|
||||||
|
---
|
||||||
|
<Layout title="Rigid's App Thing">
|
||||||
|
<NavBar slot="navbar">
|
||||||
|
<NavItems slot="navItems" />
|
||||||
|
</NavBar>
|
||||||
|
<div class="row mb-4" slot="page">
|
||||||
|
<div class="col-12">
|
||||||
|
<h1>Rigid's App Thing</h1>
|
||||||
|
<p class="text-secondary">(working title)</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<!-- src/components/FileUploader.astro -->
|
||||||
|
<form action="/api/upload" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file" accept=".csv" required />
|
||||||
|
<button type="submit">Upload CSV</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer slot="footer" />
|
||||||
|
</Layout>
|
||||||
@@ -46,7 +46,7 @@ const calculatedAt = (() => {
|
|||||||
const dates = card.prices
|
const dates = card.prices
|
||||||
.map(p => p.calculatedAt)
|
.map(p => p.calculatedAt)
|
||||||
.filter(d => d)
|
.filter(d => d)
|
||||||
.map(d => new Date(d));
|
.map(d => new Date(d!));
|
||||||
if (!dates.length) return null;
|
if (!dates.length) return null;
|
||||||
return new Date(Math.max(...dates.map(d => d.getTime())));
|
return new Date(Math.max(...dates.map(d => d.getTime())));
|
||||||
})();
|
})();
|
||||||
@@ -201,11 +201,11 @@ const altSearchUrl = (card: any) => {
|
|||||||
data-name={card?.productName}
|
data-name={card?.productName}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`/cards/${card?.productId}.jpg`}
|
src={`/static/cards/${card?.productId}.jpg`}
|
||||||
class="card-image w-100 img-fluid rounded-4"
|
class="card-image w-100 img-fluid rounded-4"
|
||||||
alt={card?.productName}
|
alt={card?.productName}
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
onerror="this.onerror=null; this.src='/cards/default.jpg'; this.closest('.image-grow, .card-image-wrap')?.setAttribute('data-default','true')"
|
onerror="this.onerror=null; this.src='/static/cards/default.jpg'; this.closest('.image-grow, .card-image-wrap')?.setAttribute('data-default','true')"
|
||||||
onclick="copyImage(this); dataLayer.push({'event': 'copiedImage'});"
|
onclick="copyImage(this); dataLayer.push({'event': 'copiedImage'});"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ const facets = searchResults.results.slice(1).map((result: any) => {
|
|||||||
<b>+/–</b>
|
<b>+/–</b>
|
||||||
</button>
|
</button>
|
||||||
<div class="card-trigger position-relative" data-card-id={card.cardId} hx-get={`/partials/card-modal?cardId=${card.cardId}`} hx-target="#cardModal" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#cardModal" onclick="const cardTitle = this.querySelector('#cardImage').getAttribute('alt'); dataLayer.push({'event': 'virtualPageview', 'pageUrl': this.getAttribute('hx-get'), 'pageTitle': cardTitle, 'previousUrl': '/pokemon'});">
|
<div class="card-trigger position-relative" data-card-id={card.cardId} hx-get={`/partials/card-modal?cardId=${card.cardId}`} hx-target="#cardModal" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#cardModal" onclick="const cardTitle = this.querySelector('#cardImage').getAttribute('alt'); dataLayer.push({'event': 'virtualPageview', 'pageUrl': this.getAttribute('hx-get'), 'pageTitle': cardTitle, 'previousUrl': '/pokemon'});">
|
||||||
<div class="image-grow rounded-4 card-image" data-energy={card.energyType} data-rarity={card.rarityName} data-variant={card.variant} data-name={card.productName}><img src={`/cards/${card.productId}.jpg`} alt={card.productName} id="cardImage" loading="lazy" decoding="async" class="img-fluid rounded-4 mb-2 w-100" onerror="this.onerror=null; this.src='/cards/default.jpg'; this.closest('.image-grow')?.setAttribute('data-default','true')"/><span class="position-absolute top-50 start-0 d-inline medium-icon"><FirstEditionIcon edition={card?.variant} /></span>
|
<div class="image-grow rounded-4 card-image" data-energy={card.energyType} data-rarity={card.rarityName} data-variant={card.variant} data-name={card.productName}><img src={`/static/cards/${card.productId}.jpg`} alt={card.productName} id="cardImage" loading="lazy" decoding="async" class="img-fluid rounded-4 mb-2 w-100" onerror="this.onerror=null; this.src='/static/cards/default.jpg'; this.closest('.image-grow')?.setAttribute('data-default','true')"/><span class="position-absolute top-50 start-0 d-inline medium-icon"><FirstEditionIcon edition={card?.variant} /></span>
|
||||||
<div class="holo-shine"></div>
|
<div class="holo-shine"></div>
|
||||||
<div class="holo-glare"></div>
|
<div class="holo-glare"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"include": [".astro/types.d.ts", "**/*"],
|
"include": [".astro/types.d.ts", "src/**/*"],
|
||||||
"exclude": ["dist"]
|
"exclude": ["dist"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user