2026-04-02 19:24:51 -04:00
import type { APIRoute } from 'astro' ;
import { db } from '../../db/index' ;
2026-04-07 09:52:17 -04:00
import { inventory , priceHistory } from '../../db/schema' ;
2026-04-02 19:24:51 -04:00
import { client } from '../../db/typesense' ;
2026-04-06 14:31:39 -04:00
import { eq } from 'drizzle-orm' ;
2026-04-02 19:24:51 -04:00
2026-04-05 16:09:52 -04:00
const GainLoss = ( purchasePrice : any , marketPrice : any ) = > {
2026-04-02 19:24:51 -04:00
if ( ! purchasePrice || ! marketPrice ) return '<div class="fs-5 fw-semibold">N/A</div>' ;
const pp = Number ( purchasePrice ) ;
const mp = Number ( marketPrice ) ;
if ( pp === mp ) return '<div class="fs-5 fw-semibold text-warning">-</div>' ;
2026-04-05 16:09:52 -04:00
if ( pp > mp ) return ` <div class="fs-5 fw-semibold text-critical">- $ ${ ( pp - mp ) . toFixed ( 2 ) } </div> ` ;
return ` <div class="fs-6 fw-semibold text-success">+ $ ${ ( mp - pp ) . toFixed ( 2 ) } </div> ` ;
2026-04-02 19:24:51 -04:00
}
2026-04-05 16:09:52 -04:00
const getInventory = async ( userId : string , cardId : number ) = > {
2026-04-07 09:52:17 -04:00
const card = await db . query . cards . findFirst ( {
where : { cardId : cardId , } ,
with : { prices : {
with : { inventories : { where : { userId : userId } } , }
} , } ,
2026-04-06 14:31:39 -04:00
} ) ;
2026-04-02 19:24:51 -04:00
2026-04-07 09:52:17 -04:00
const invHtml = card ? . prices ? . flatMap ( price = > price . inventories . map ( inv = > {
const marketPrice = price . marketPrice ;
2026-04-05 16:09:52 -04:00
const marketPriceDisplay = marketPrice ? ` $ ${ marketPrice } ` : '—' ;
const purchasePriceDisplay = inv . purchasePrice ? ` $ ${ Number ( inv . purchasePrice ) . toFixed ( 2 ) } ` : '—' ;
2026-04-02 19:24:51 -04:00
return `
2026-04-05 16:32:44 -04:00
< article class = "border rounded-4 p-2 inventory-entry-card"
2026-04-03 22:50:54 -04:00
data - inventory - id = "${inv.inventoryId}"
2026-04-07 09:52:17 -04:00
data - card - id = "${price.cardId}"
2026-04-03 22:50:54 -04:00
data - purchase - price = "${inv.purchasePrice}"
data - note = "${(inv.note || '').replace(/" / g , '"' ) } " >
2026-04-05 16:09:52 -04:00
< div class = "d-flex flex-column" >
2026-04-02 19:24:51 -04:00
<!-- Top row -->
2026-04-05 16:09:52 -04:00
< div class = "d-flex justify-content-between gap-3" >
2026-04-02 19:24:51 -04:00
< div class = "min-w-0 flex-grow-1" >
2026-04-07 09:52:17 -04:00
< div class = "fw-semibold fs-6 text-body mb-1" > $ { price . condition } < / div >
2026-04-02 19:24:51 -04:00
< / div >
2026-04-05 16:09:52 -04:00
< div class = "fs-7 text-secondary" > Added : $ { inv . createdAt ? new Date ( inv . createdAt ) . toLocaleDateString ( ) : '—' } < / div >
2026-04-02 19:24:51 -04:00
< / div >
<!-- Middle row -->
< div class = "row g-2" >
< div class = "col-4" >
< div class = "small text-secondary" > Purchase price < / div >
2026-04-05 16:09:52 -04:00
< div class = "fs-6 fw-semibold" > $ { purchasePriceDisplay } < / div >
2026-04-02 19:24:51 -04:00
< / div >
< div class = "col-4" >
< div class = "small text-secondary" > Market price < / div >
2026-04-05 16:09:52 -04:00
< div class = "fs-6 text-success" > $ { marketPriceDisplay } < / div >
2026-04-02 19:24:51 -04:00
< / div >
< div class = "col-4" >
< div class = "small text-secondary" > Gain / loss < / div >
2026-04-05 16:09:52 -04:00
$ { GainLoss ( inv . purchasePrice , marketPrice ) }
2026-04-02 19:24:51 -04:00
< / div >
< / div >
<!-- Bottom row -->
2026-04-05 16:09:52 -04:00
< div class = "d-flex justify-content-between align-items-center gap-3 flex-wrap mt-2" >
2026-04-02 19:24:51 -04:00
< div class = "d-flex align-items-center gap-2" >
< span class = "small text-secondary" > Qty < / span >
< div class = "btn-group" role = "group" aria-label = "Quantity controls" >
2026-04-03 22:50:54 -04:00
< button type = "button" class = "btn btn-outline-secondary btn-sm" data-inv-action = "decrement" > − < / button >
< button type = "button" class = "btn btn-outline-secondary btn-sm" tabindex = "-1" data - inv - qty > $ { inv . quantity } < / button >
< button type = "button" class = "btn btn-outline-secondary btn-sm" data-inv-action = "increment" > + < / button >
2026-04-02 19:24:51 -04:00
< / div >
< / div >
< div class = "d-flex align-items-center gap-2 flex-wrap" >
2026-04-03 22:50:54 -04:00
< button type = "button" class = "btn btn-sm btn-outline-secondary" data-inv-action = "update" > Edit < / button >
2026-04-05 16:09:52 -04:00
< button type = "button" class = "btn btn-sm btn-outline-danger" data-inv-action = "remove" onclick = "if(!confirm('Are you sure you want to remove this card from your inventory?')) event.stopImmediatePropagation();" > Remove < / button >
2026-04-02 19:24:51 -04:00
< / div >
< / div >
< / div >
< / article > ` ;
2026-04-07 09:52:17 -04:00
} ) ) || [ ] ;
2026-04-02 19:24:51 -04:00
return new Response (
invHtml . join ( '' ) ,
{
status : 200 ,
headers : { 'Content-Type' : 'text/html' } ,
}
) ;
}
2026-04-07 09:52:17 -04:00
const addToInventory = async ( userId : string , skuId : number , purchasePrice : number , quantity : number , note : string , catalogName : string ) = > {
2026-04-06 14:31:39 -04:00
// First add to database
2026-04-02 19:24:51 -04:00
const inv = await db . insert ( inventory ) . values ( {
userId : userId ,
2026-04-07 09:52:17 -04:00
skuId : skuId ,
2026-04-02 19:24:51 -04:00
catalogName : catalogName ,
2026-04-06 14:31:39 -04:00
purchasePrice : purchasePrice.toFixed ( 2 ) ,
2026-04-02 19:24:51 -04:00
quantity : quantity ,
note : note ,
} ) . returning ( ) ;
2026-04-06 14:31:39 -04:00
// And then add to Typesense for searching
2026-04-02 19:24:51 -04:00
await client . collections ( 'inventories' ) . documents ( ) . import ( inv . map ( i = > ( {
id : i.inventoryId ,
userId : i.userId ,
catalogName : i.catalogName ,
2026-04-07 09:52:17 -04:00
sku_id : i.skuId.toString ( ) ,
2026-04-02 19:24:51 -04:00
} ) ) ) ;
}
2026-04-05 16:09:52 -04:00
const removeFromInventory = async ( inventoryId : string ) = > {
await db . delete ( inventory ) . where ( eq ( inventory . inventoryId , inventoryId ) ) ;
2026-04-02 19:24:51 -04:00
await client . collections ( 'inventories' ) . documents ( inventoryId ) . delete ( ) ;
}
2026-04-05 16:09:52 -04:00
const updateInventory = async ( inventoryId : string , quantity : number , purchasePrice : number , note : string ) = > {
2026-04-06 14:31:39 -04:00
// Update the database
2026-04-02 19:24:51 -04:00
await db . update ( inventory ) . set ( {
quantity : quantity ,
2026-04-06 14:31:39 -04:00
purchasePrice : purchasePrice.toFixed ( 2 ) ,
2026-04-02 19:24:51 -04:00
note : note ,
2026-04-05 16:09:52 -04:00
} ) . where ( eq ( inventory . inventoryId , inventoryId ) ) ;
2026-04-06 14:31:39 -04:00
// No need to update Typesense since we don't search by quantity or price
2026-04-02 19:24:51 -04:00
}
export const POST : APIRoute = async ( { request , locals } ) = > {
2026-04-06 14:31:39 -04:00
// Access form data from the request body
2026-04-02 19:24:51 -04:00
const formData = await request . formData ( ) ;
const action = formData . get ( 'action' ) ;
const cardId = Number ( formData . get ( 'cardId' ) ) || 0 ;
const { userId } = locals . auth ( ) ;
switch ( action ) {
case 'add' :
const purchasePrice = Number ( formData . get ( 'purchasePrice' ) ) || 0 ;
const quantity = Number ( formData . get ( 'quantity' ) ) || 1 ;
const note = formData . get ( 'note' ) ? . toString ( ) || '' ;
const catalogName = formData . get ( 'catalogName' ) ? . toString ( ) || 'Default' ;
2026-04-07 09:52:17 -04:00
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 ) ;
2026-04-02 19:24:51 -04:00
break ;
case 'remove' :
const inventoryId = formData . get ( 'inventoryId' ) ? . toString ( ) || '' ;
await removeFromInventory ( inventoryId ) ;
break ;
case 'update' :
const invId = formData . get ( 'inventoryId' ) ? . toString ( ) || '' ;
const qty = Number ( formData . get ( 'quantity' ) ) || 1 ;
const price = Number ( formData . get ( 'purchasePrice' ) ) || 0 ;
const invNote = formData . get ( 'note' ) ? . toString ( ) || '' ;
await updateInventory ( invId , qty , price , invNote ) ;
break ;
default :
2026-04-06 14:31:39 -04:00
// No action = list inventory for this card
2026-04-03 22:10:41 -04:00
return getInventory ( userId ! , cardId ) ;
2026-04-02 19:24:51 -04:00
}
2026-04-06 14:31:39 -04:00
// Always return current inventory after a mutation
2026-04-02 19:24:51 -04:00
return getInventory ( userId ! , cardId ) ;
} ;