You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
5.3 KiB
204 lines
5.3 KiB
import type { H3Event } from 'h3'
|
|
import { and, eq, inArray, asc } from 'drizzle-orm'
|
|
import { carts, cartItems, products } from '../database/schema'
|
|
|
|
// Re-export shared types
|
|
export type { CartItemWithProduct, CartSummary } from '~/types/cart'
|
|
import type { CartItemWithProduct, CartSummary } from '~/types/cart'
|
|
|
|
/**
|
|
* Get or create a cart for the current user/session
|
|
*
|
|
* @param event - H3 event object
|
|
* @returns Cart record
|
|
*/
|
|
export async function getOrCreateCart(event: H3Event) {
|
|
const db = useDatabase()
|
|
const { user } = await getUserSession(event)
|
|
|
|
if (user) {
|
|
// Authenticated user - find or create cart by userId
|
|
let cart = await db.query.carts.findFirst({
|
|
where: eq(carts.userId, user.id),
|
|
})
|
|
|
|
if (!cart) {
|
|
// Create new cart for user
|
|
const [newCart] = await db
|
|
.insert(carts)
|
|
.values({
|
|
userId: user.id,
|
|
sessionId: '', // Empty for user carts (not used)
|
|
})
|
|
.returning()
|
|
cart = newCart
|
|
}
|
|
|
|
return cart
|
|
} else {
|
|
// Guest user - find or create cart by sessionId
|
|
const sessionId = getOrCreateSessionId(event)
|
|
|
|
let cart = await db.query.carts.findFirst({
|
|
where: and(eq(carts.sessionId, sessionId), eq(carts.userId, null)),
|
|
})
|
|
|
|
if (!cart) {
|
|
// Create new cart for guest
|
|
const [newCart] = await db
|
|
.insert(carts)
|
|
.values({
|
|
userId: null,
|
|
sessionId,
|
|
})
|
|
.returning()
|
|
cart = newCart
|
|
}
|
|
|
|
return cart
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cart with all items and product details
|
|
*
|
|
* Automatically filters out unavailable products (inactive or out of stock)
|
|
* and removes them from the cart.
|
|
*
|
|
* @param cartId - Cart UUID
|
|
* @returns Cart summary with items, totals, and removed items
|
|
*/
|
|
export async function getCartWithItems(cartId: string): Promise<CartSummary> {
|
|
const db = useDatabase()
|
|
|
|
// Fetch cart
|
|
const cart = await db.query.carts.findFirst({
|
|
where: eq(carts.id, cartId),
|
|
})
|
|
|
|
if (!cart) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: 'Cart not found',
|
|
})
|
|
}
|
|
|
|
// Fetch cart items with product details
|
|
const items = await db.query.cartItems.findMany({
|
|
where: eq(cartItems.cartId, cartId),
|
|
with: {
|
|
product: true,
|
|
},
|
|
orderBy: asc(cartItems.addedAt), // Sort by addedAt to maintain stable order
|
|
})
|
|
|
|
// Separate available and unavailable items
|
|
const availableItems: CartItemWithProduct[] = []
|
|
const unavailableItemIds: string[] = []
|
|
const removedProductNames: string[] = []
|
|
|
|
for (const item of items) {
|
|
// Check if product is available
|
|
const isAvailable = item.product.active && item.product.stockQuantity >= item.quantity
|
|
|
|
if (isAvailable) {
|
|
// Add to available items with subtotal calculation
|
|
availableItems.push({
|
|
id: item.id,
|
|
cartId: item.cartId,
|
|
productId: item.productId,
|
|
quantity: item.quantity,
|
|
addedAt: item.addedAt,
|
|
product: {
|
|
id: item.product.id,
|
|
name: item.product.name,
|
|
description: item.product.description,
|
|
price: item.product.price,
|
|
stockQuantity: item.product.stockQuantity,
|
|
active: item.product.active,
|
|
category: item.product.category,
|
|
imageUrl: item.product.imageUrl,
|
|
navProductId: item.product.navProductId,
|
|
},
|
|
subtotal: Number.parseFloat(item.product.price) * item.quantity,
|
|
})
|
|
} else {
|
|
// Mark for removal
|
|
unavailableItemIds.push(item.id)
|
|
removedProductNames.push(item.product.name)
|
|
}
|
|
}
|
|
|
|
// Remove unavailable items from cart
|
|
if (unavailableItemIds.length > 0) {
|
|
await db.delete(cartItems).where(inArray(cartItems.id, unavailableItemIds))
|
|
|
|
// Update cart's updatedAt timestamp
|
|
await db
|
|
.update(carts)
|
|
.set({ updatedAt: new Date() })
|
|
.where(eq(carts.id, cartId))
|
|
}
|
|
|
|
// Calculate total
|
|
const total = availableItems.reduce((sum, item) => sum + item.subtotal, 0)
|
|
const itemCount = availableItems.reduce((sum, item) => sum + item.quantity, 0)
|
|
|
|
return {
|
|
cart,
|
|
items: availableItems,
|
|
total,
|
|
itemCount,
|
|
...(removedProductNames.length > 0 && { removedItems: removedProductNames }),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update cart's updated_at timestamp
|
|
*
|
|
* @param cartId - Cart UUID
|
|
*/
|
|
export async function touchCart(cartId: string): Promise<void> {
|
|
const db = useDatabase()
|
|
await db
|
|
.update(carts)
|
|
.set({ updatedAt: new Date() })
|
|
.where(eq(carts.id, cartId))
|
|
}
|
|
|
|
/**
|
|
* Check if a cart item belongs to the current user/session
|
|
*
|
|
* @param event - H3 event object
|
|
* @param cartItemId - Cart item UUID
|
|
* @returns true if item belongs to current user/session, false otherwise
|
|
*/
|
|
export async function verifyCartItemOwnership(
|
|
event: H3Event,
|
|
cartItemId: string
|
|
): Promise<boolean> {
|
|
const db = useDatabase()
|
|
const { user } = await getUserSession(event)
|
|
|
|
// Fetch cart item with cart details
|
|
const item = await db.query.cartItems.findFirst({
|
|
where: eq(cartItems.id, cartItemId),
|
|
with: {
|
|
cart: true,
|
|
},
|
|
})
|
|
|
|
if (!item) {
|
|
return false
|
|
}
|
|
|
|
// Check ownership
|
|
if (user) {
|
|
// Authenticated user - check userId match
|
|
return item.cart.userId === user.id
|
|
} else {
|
|
// Guest user - check sessionId match
|
|
const sessionId = getSessionId(event)
|
|
return sessionId !== null && item.cart.sessionId === sessionId && item.cart.userId === null
|
|
}
|
|
}
|
|
|