feat: initial commit — storefront, convex backend, and shared packages

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>
This commit is contained in:
2026-03-04 09:31:18 +03:00
commit cc15338ad9
361 changed files with 45005 additions and 0 deletions

38
convex/model/users.ts Normal file
View File

@@ -0,0 +1,38 @@
import { QueryCtx, MutationCtx } from "../_generated/server";
import type { Id } from "../_generated/dataModel";
type AuthCtx = QueryCtx | MutationCtx;
export async function getCurrentUser(ctx: QueryCtx) {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return null;
return await ctx.db
.query("users")
.withIndex("by_external_id", (q) => q.eq("externalId", identity.subject))
.unique();
}
export async function getCurrentUserOrThrow(ctx: AuthCtx) {
const user = await getCurrentUser(ctx as QueryCtx);
if (!user) throw new Error("Unauthenticated");
return user;
}
export async function requireAdmin(ctx: QueryCtx) {
const user = await getCurrentUserOrThrow(ctx);
if (user.role !== "admin" && user.role !== "super_admin") {
throw new Error("Unauthorized: admin access required");
}
return user;
}
export async function requireOwnership(
ctx: AuthCtx,
resourceUserId: Id<"users">,
) {
const user = await getCurrentUserOrThrow(ctx);
if (resourceUserId !== user._id) {
throw new Error("Unauthorized: resource does not belong to you");
}
return user;
}