Completes the first milestone of The Pet Loft ecommerce platform: - apps/storefront: full customer-facing Next.js app with HeroUI (cart, checkout, orders, wishlist, product detail, shop, search, auth) - convex/: serverless backend with schema, queries, mutations, actions, HTTP routes, Stripe/Shippo integrations, and co-located tests - packages/types, packages/utils, packages/convex: shared workspace packages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
2.1 KiB
TypeScript
81 lines
2.1 KiB
TypeScript
import { MutationCtx, QueryCtx } from "../_generated/server";
|
|
import { Id } from "../_generated/dataModel";
|
|
|
|
const CART_EXPIRY_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
|
|
type CartReadCtx = Pick<QueryCtx, "db">;
|
|
|
|
/**
|
|
* Get cart by userId or sessionId (read-only). Returns null if not found.
|
|
*/
|
|
export async function getCart(
|
|
ctx: CartReadCtx,
|
|
userId?: Id<"users">,
|
|
sessionId?: string
|
|
) {
|
|
if (userId) {
|
|
return await ctx.db
|
|
.query("carts")
|
|
.withIndex("by_user", (q) => q.eq("userId", userId))
|
|
.unique();
|
|
}
|
|
if (sessionId) {
|
|
return await ctx.db
|
|
.query("carts")
|
|
.withIndex("by_session", (q) => q.eq("sessionId", sessionId))
|
|
.unique();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get existing cart or create a new one. Mutation-only; use from addItem, updateItem, removeItem, clear, merge.
|
|
* At least one of userId or sessionId must be provided.
|
|
*/
|
|
export async function getOrCreateCart(
|
|
ctx: MutationCtx,
|
|
userId?: Id<"users">,
|
|
sessionId?: string
|
|
): Promise<{ _id: Id<"carts">; items: { productId: Id<"products">; variantId?: Id<"productVariants">; quantity: number; price: number }[] }> {
|
|
if (!userId && !sessionId) {
|
|
throw new Error("Either userId or sessionId must be provided");
|
|
}
|
|
|
|
const now = Date.now();
|
|
const expiresAt = now + CART_EXPIRY_MS;
|
|
|
|
if (userId) {
|
|
const existing = await ctx.db
|
|
.query("carts")
|
|
.withIndex("by_user", (q) => q.eq("userId", userId))
|
|
.unique();
|
|
if (existing) return existing;
|
|
const id = await ctx.db.insert("carts", {
|
|
userId,
|
|
items: [],
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
expiresAt,
|
|
});
|
|
const cart = await ctx.db.get(id);
|
|
if (!cart) throw new Error("Failed to create cart");
|
|
return cart;
|
|
}
|
|
|
|
const existing = await ctx.db
|
|
.query("carts")
|
|
.withIndex("by_session", (q) => q.eq("sessionId", sessionId!))
|
|
.unique();
|
|
if (existing) return existing;
|
|
const id = await ctx.db.insert("carts", {
|
|
sessionId: sessionId!,
|
|
items: [],
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
expiresAt,
|
|
});
|
|
const cart = await ctx.db.get(id);
|
|
if (!cart) throw new Error("Failed to create cart");
|
|
return cart;
|
|
}
|