Implement shopping cart functionality with UI components and API integration

- Added CartItem, CartSummary, CartEmpty, CartSidebar, and CartSheet components for managing cart display and interactions.
- Integrated useCart and useCartUI composables for cart state management and UI control.
- Implemented API endpoints for cart operations, including fetching, adding, updating, and removing items.
- Enhanced user experience with loading states and notifications using vue-sonner for cart actions.
- Configured session management for guest and authenticated users, ensuring cart persistence across sessions.

This commit completes the shopping cart feature, enabling users to add items, view their cart, and proceed to checkout.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Bastian Masanek
2025-11-03 12:43:13 +01:00
parent 9d0e77fc98
commit b372e2cf78
44 changed files with 2209 additions and 123 deletions

View File

@@ -0,0 +1,116 @@
import { and, lt, isNull } from 'drizzle-orm'
import { carts } from '../database/schema'
/**
* Cart Cleanup Utilities
*
* These functions prepare the structure for automatic cart cleanup.
* The actual cleanup job will be implemented in a later phase using BullMQ.
*
* Cleanup Strategy:
* - User carts: Keep until updated_at > CART_EXPIRY_DAYS
* - Guest carts: Keep until updated_at > CART_EXPIRY_DAYS
* - Rationale: Inactive carts consume database space and should be pruned
*
* Future Implementation:
* - BullMQ scheduled job runs daily at night (e.g., 3 AM)
* - Calls getExpiredCarts() to find carts to delete
* - Deletes expired carts (cascade deletes cart_items automatically)
* - Logs cleanup statistics for monitoring
*/
/**
* Get carts that are older than the configured expiry period
*
* @returns Array of expired cart IDs
*/
export async function getExpiredCarts(): Promise<string[]> {
const db = useDatabase()
const config = useRuntimeConfig()
// Calculate expiry date
const expiryDays = config.cart.expiryDays
const expiryDate = new Date()
expiryDate.setDate(expiryDate.getDate() - expiryDays)
// Find carts not updated since expiry date
const expiredCarts = await db
.select({ id: carts.id })
.from(carts)
.where(lt(carts.updatedAt, expiryDate))
return expiredCarts.map((cart) => cart.id)
}
/**
* Delete expired carts
*
* Note: cart_items are automatically deleted via CASCADE foreign key constraint
*
* @param cartIds - Array of cart UUIDs to delete
* @returns Number of carts deleted
*/
export async function deleteExpiredCarts(cartIds: string[]): Promise<number> {
if (cartIds.length === 0) {
return 0
}
const db = useDatabase()
// Delete carts (cart_items cascade automatically)
const result = await db
.delete(carts)
.where(
and(
...cartIds.map((id) => eq(carts.id, id))
)
)
return cartIds.length
}
/**
* Get cleanup statistics
*
* @returns Statistics about carts in the database
*/
export async function getCartStatistics() {
const db = useDatabase()
const config = useRuntimeConfig()
// Calculate expiry date
const expiryDays = config.cart.expiryDays
const expiryDate = new Date()
expiryDate.setDate(expiryDate.getDate() - expiryDays)
// Count carts by type
const [totalCarts] = await db.select({ count: count() }).from(carts)
const [userCarts] = await db
.select({ count: count() })
.from(carts)
.where(isNull(carts.userId).not())
const [guestCarts] = await db
.select({ count: count() })
.from(carts)
.where(isNull(carts.userId))
const [expiredCarts] = await db
.select({ count: count() })
.from(carts)
.where(lt(carts.updatedAt, expiryDate))
return {
totalCarts: totalCarts?.count || 0,
userCarts: userCarts?.count || 0,
guestCarts: guestCarts?.count || 0,
expiredCarts: expiredCarts?.count || 0,
expiryDays,
expiryDate: expiryDate.toISOString(),
}
}
// Note: Import count function
import { count } from 'drizzle-orm'
import { eq } from 'drizzle-orm'