[chore] move inventory relationship to sku

This commit is contained in:
2026-04-07 09:52:17 -04:00
parent 5dc7ce2de7
commit cb829e1922
4 changed files with 34 additions and 31 deletions

View File

@@ -62,7 +62,7 @@ export const createCardCollection = async () => {
{ name: 'releaseDate', type: 'int32' }, { name: 'releaseDate', type: 'int32' },
{ name: 'marketPrice', type: 'int32', optional: true, sort: true }, { name: 'marketPrice', type: 'int32', optional: true, sort: true },
{ name: 'content', type: 'string', token_separators: ['/'] }, { name: 'content', type: 'string', token_separators: ['/'] },
{ name: 'sku_id', type: 'string[]', optional: true, reference: 'skus.id', async_reference: true } // { name: 'sku_id', type: 'string[]', optional: true, reference: 'skus.id', async_reference: true }
], ],
}); });
console.log(chalk.green('Collection "cards" created successfully.')); console.log(chalk.green('Collection "cards" created successfully.'));
@@ -83,6 +83,7 @@ export const createSkuCollection = async () => {
{ name: 'highestPrice', type: 'int32', optional: true }, { name: 'highestPrice', type: 'int32', optional: true },
{ name: 'lowestPrice', type: 'int32', optional: true }, { name: 'lowestPrice', type: 'int32', optional: true },
{ name: 'marketPrice', type: 'int32', optional: true }, { name: 'marketPrice', type: 'int32', optional: true },
{ name: 'card_id', type: 'string', reference: 'cards.id' },
] ]
}); });
console.log(chalk.green('Collection "skus" created successfully.')); console.log(chalk.green('Collection "skus" created successfully.'));
@@ -101,7 +102,7 @@ export const createInventoryCollection = async () => {
{ name: 'id', type: 'string' }, { name: 'id', type: 'string' },
{ name: 'userId', type: 'string' }, { name: 'userId', type: 'string' },
{ name: 'catalogName', type: 'string' }, { name: 'catalogName', type: 'string' },
{ name: 'card_id', type: 'string', reference: 'cards.id' }, { name: 'sku_id', type: 'string', reference: 'skus.id' },
] ]
}); });
console.log(chalk.green('Collection "inventories" created successfully.')); console.log(chalk.green('Collection "inventories" created successfully.'));
@@ -132,7 +133,7 @@ export const upsertCardCollection = async (db:DBInstance) => {
content: [card.productName, card.productLineName, card.set?.setName || "", card.number, card.rarityName, card.artist || ""].join(' '), content: [card.productName, card.productLineName, card.set?.setName || "", card.number, card.rarityName, card.artist || ""].join(' '),
releaseDate: card.tcgdata?.releaseDate ? Math.floor(new Date(card.tcgdata.releaseDate).getTime() / 1000) : 0, releaseDate: card.tcgdata?.releaseDate ? Math.floor(new Date(card.tcgdata.releaseDate).getTime() / 1000) : 0,
...(marketPrice !== null && { marketPrice }), ...(marketPrice !== null && { marketPrice }),
sku_id: card.prices.map(price => price.skuId.toString()) // sku_id: card.prices.map(price => price.skuId.toString())
}; };
}), { action: 'upsert' }); }), { action: 'upsert' });
console.log(chalk.green('Collection "cards" indexed successfully.')); console.log(chalk.green('Collection "cards" indexed successfully.'));
@@ -146,6 +147,7 @@ export const upsertSkuCollection = async (db:DBInstance) => {
highestPrice: DollarToInt(sku.highestPrice), highestPrice: DollarToInt(sku.highestPrice),
lowestPrice: DollarToInt(sku.lowestPrice), lowestPrice: DollarToInt(sku.lowestPrice),
marketPrice: DollarToInt(sku.marketPrice), marketPrice: DollarToInt(sku.marketPrice),
card_id: sku.cardId.toString(),
})), { action: 'upsert' }); })), { action: 'upsert' });
console.log(chalk.green('Collection "skus" indexed successfully.')); console.log(chalk.green('Collection "skus" indexed successfully.'));
} }
@@ -156,7 +158,7 @@ export const upsertInventoryCollection = async (db:DBInstance) => {
id: i.inventoryId, id: i.inventoryId,
userId: i.userId, userId: i.userId,
catalogName: i.catalogName, catalogName: i.catalogName,
card_id: i.cardId.toString(), sku_id: i.skuId.toString(),
})), { action: 'upsert' }); })), { action: 'upsert' });
console.log(chalk.green('Collection "inventories" indexed successfully.')); console.log(chalk.green('Collection "inventories" indexed successfully.'));
} }

View File

@@ -24,18 +24,13 @@ export const relations = defineRelations(schema, (r) => ({
inventories: r.many.inventory(), inventories: r.many.inventory(),
}, },
inventory: { inventory: {
card: r.one.cards({ card: r.one.skus({
from: r.inventory.cardId, from: r.inventory.skuId,
to: r.cards.cardId, to: r.skus.skuId,
}),
sku: r.one.skus({
from: [r.inventory.cardId, r.inventory.condition],
to: [r.skus.cardId, r.skus.condition],
}), }),
}, },
cards: { cards: {
prices: r.many.skus(), prices: r.many.skus(),
inventories: r.many.inventory(),
set: r.one.sets({ set: r.one.sets({
from: r.cards.setId, from: r.cards.setId,
to: r.sets.setId, to: r.sets.setId,

View File

@@ -129,15 +129,14 @@ export const inventory = pokeSchema.table('inventory',{
inventoryId: uuid().primaryKey().notNull().defaultRandom(), inventoryId: uuid().primaryKey().notNull().defaultRandom(),
userId: varchar({ length: 100 }).notNull(), userId: varchar({ length: 100 }).notNull(),
catalogName: varchar({ length: 100 }), catalogName: varchar({ length: 100 }),
cardId: integer().notNull(), skuId: integer().notNull(),
condition: varchar({ length: 255 }).notNull(),
quantity: integer(), quantity: integer(),
purchasePrice: decimal({ precision: 10, scale: 2 }), purchasePrice: decimal({ precision: 10, scale: 2 }),
note: varchar({ length:255 }), note: varchar({ length:255 }),
createdAt: timestamp().notNull().defaultNow(), createdAt: timestamp().notNull().defaultNow(),
}, },
(table) => [ (table) => [
index('idx_userid_cardid').on(table.userId, table.cardId) index('idx_userid_skuId').on(table.userId, table.skuId)
]); ]);
export const processingSkus = pokeSchema.table('processing_skus', { export const processingSkus = pokeSchema.table('processing_skus', {

View File

@@ -1,6 +1,6 @@
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
import { db } from '../../db/index'; import { db } from '../../db/index';
import { inventory } from '../../db/schema'; import { inventory, priceHistory } from '../../db/schema';
import { client } from '../../db/typesense'; import { client } from '../../db/typesense';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
@@ -15,27 +15,29 @@ const GainLoss = (purchasePrice: any, marketPrice: any) => {
const getInventory = async (userId: string, cardId: number) => { const getInventory = async (userId: string, cardId: number) => {
const inventories = await db.query.inventory.findMany({ const card = await db.query.cards.findFirst({
where: { userId:userId, cardId:cardId, }, where: { cardId: cardId, },
with: { card: true, sku: true, } with : { prices: {
with: { inventories: { where: { userId: userId } }, }
}, },
}); });
const invHtml = inventories.map(inv => { const invHtml = card?.prices?.flatMap(price => price.inventories.map(inv => {
const marketPrice = inv.sku?.marketPrice; const marketPrice = price.marketPrice;
const marketPriceDisplay = marketPrice ? `$${marketPrice}` : '—'; const marketPriceDisplay = marketPrice ? `$${marketPrice}` : '—';
const purchasePriceDisplay = inv.purchasePrice ? `$${Number(inv.purchasePrice).toFixed(2)}` : '—'; const purchasePriceDisplay = inv.purchasePrice ? `$${Number(inv.purchasePrice).toFixed(2)}` : '—';
return ` return `
<article class="border rounded-4 p-2 inventory-entry-card" <article class="border rounded-4 p-2 inventory-entry-card"
data-inventory-id="${inv.inventoryId}" data-inventory-id="${inv.inventoryId}"
data-card-id="${inv.cardId}" data-card-id="${price.cardId}"
data-purchase-price="${inv.purchasePrice}" data-purchase-price="${inv.purchasePrice}"
data-note="${(inv.note || '').replace(/"/g, '&quot;')}"> data-note="${(inv.note || '').replace(/"/g, '&quot;')}">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<!-- Top row --> <!-- Top row -->
<div class="d-flex justify-content-between gap-3"> <div class="d-flex justify-content-between gap-3">
<div class="min-w-0 flex-grow-1"> <div class="min-w-0 flex-grow-1">
<div class="fw-semibold fs-6 text-body mb-1">${inv.condition}</div> <div class="fw-semibold fs-6 text-body mb-1">${price.condition}</div>
</div> </div>
<div class="fs-7 text-secondary">Added: ${inv.createdAt ? new Date(inv.createdAt).toLocaleDateString() : '—'}</div> <div class="fs-7 text-secondary">Added: ${inv.createdAt ? new Date(inv.createdAt).toLocaleDateString() : '—'}</div>
</div> </div>
@@ -71,7 +73,7 @@ const getInventory = async (userId: string, cardId: number) => {
</div> </div>
</div> </div>
</article>`; </article>`;
}); })) || [];
return new Response( return new Response(
invHtml.join(''), invHtml.join(''),
@@ -83,13 +85,12 @@ const getInventory = async (userId: string, cardId: number) => {
} }
const addToInventory = async (userId: string, cardId: number, condition: string, variant: string, purchasePrice: number, quantity: number, note: string, catalogName: string) => { const addToInventory = async (userId: string, skuId: number, purchasePrice: number, quantity: number, note: string, catalogName: string) => {
// First add to database // First add to database
const inv = await db.insert(inventory).values({ const inv = await db.insert(inventory).values({
userId: userId, userId: userId,
cardId: cardId, skuId: skuId,
catalogName: catalogName, catalogName: catalogName,
condition: condition,
purchasePrice: purchasePrice.toFixed(2), purchasePrice: purchasePrice.toFixed(2),
quantity: quantity, quantity: quantity,
note: note, note: note,
@@ -99,7 +100,7 @@ const addToInventory = async (userId: string, cardId: number, condition: string,
id: i.inventoryId, id: i.inventoryId,
userId: i.userId, userId: i.userId,
catalogName: i.catalogName, catalogName: i.catalogName,
card_id: i.cardId.toString(), sku_id: i.skuId.toString(),
}))); })));
} }
@@ -128,13 +129,19 @@ export const POST: APIRoute = async ({ request, locals }) => {
switch (action) { switch (action) {
case 'add': case 'add':
const condition = formData.get('condition')?.toString() || 'Unknown';
const variant = formData.get('variant')?.toString() || 'Normal';
const purchasePrice = Number(formData.get('purchasePrice')) || 0; const purchasePrice = Number(formData.get('purchasePrice')) || 0;
const quantity = Number(formData.get('quantity')) || 1; const quantity = Number(formData.get('quantity')) || 1;
const note = formData.get('note')?.toString() || ''; const note = formData.get('note')?.toString() || '';
const catalogName = formData.get('catalogName')?.toString() || 'Default'; const catalogName = formData.get('catalogName')?.toString() || 'Default';
await addToInventory(userId!, cardId, condition, variant, purchasePrice, quantity, note, catalogName); const condition = formData.get('condition')?.toString() || 'Near Mint';
const skuId = await db.query.skus.findFirst({
where: { cardId: cardId, condition: condition },
columns: { skuId: true },
}).then(sku => sku?.skuId);
if (!skuId) {
return new Response('SKU not found for card', { status: 404 });
}
await addToInventory(userId!, skuId, purchasePrice, quantity, note, catalogName);
break; break;
case 'remove': case 'remove':