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:
120
convex/categories.ts
Normal file
120
convex/categories.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { query, mutation } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import * as Users from "./model/users";
|
||||
import * as Categories from "./model/categories";
|
||||
|
||||
export const list = query({
|
||||
args: {
|
||||
parentId: v.optional(v.id("categories")),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
let items;
|
||||
console.log("args in list", args);
|
||||
if (args.parentId !== undefined) {
|
||||
items = await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_parent", (q) => q.eq("parentId", args.parentId!))
|
||||
.collect();
|
||||
} else {
|
||||
items = await ctx.db.query("categories").collect();
|
||||
}
|
||||
items.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return items;
|
||||
},
|
||||
});
|
||||
|
||||
export const getById = query({
|
||||
args: { id: v.id("categories") },
|
||||
handler: async (ctx, { id }) => {
|
||||
return await ctx.db.get(id);
|
||||
},
|
||||
});
|
||||
|
||||
export const getBySlug = query({
|
||||
args: { slug: v.string() },
|
||||
handler: async (ctx, { slug }) => {
|
||||
return await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_slug", (q) => q.eq("slug", slug))
|
||||
.unique();
|
||||
},
|
||||
});
|
||||
|
||||
export const getByPath = query({
|
||||
args: {
|
||||
categorySlug: v.string(),
|
||||
subCategorySlug: v.string(),
|
||||
},
|
||||
handler: async (ctx, { categorySlug, subCategorySlug }) => {
|
||||
const parent = await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_slug", (q) => q.eq("slug", categorySlug))
|
||||
.unique();
|
||||
if (!parent) return null;
|
||||
return await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_parent_slug", (q) =>
|
||||
q.eq("parentId", parent._id).eq("slug", subCategorySlug),
|
||||
)
|
||||
.unique();
|
||||
},
|
||||
});
|
||||
|
||||
export const listByTopCategory = query({
|
||||
args: { slug: v.string() },
|
||||
handler: async (ctx, { slug }) => {
|
||||
const items = await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_top_category_slug", (q) =>
|
||||
q.eq("topCategorySlug", slug),
|
||||
)
|
||||
.collect();
|
||||
items.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return items;
|
||||
},
|
||||
});
|
||||
|
||||
export const create = mutation({
|
||||
args: {
|
||||
name: v.string(),
|
||||
slug: v.string(),
|
||||
description: v.optional(v.string()),
|
||||
parentId: v.optional(v.id("categories")),
|
||||
topCategorySlug: v.optional(v.string()),
|
||||
seoTitle: v.optional(v.string()),
|
||||
seoDescription: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
await Users.requireAdmin(ctx);
|
||||
const existing = await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_slug", (q) => q.eq("slug", args.slug))
|
||||
.unique();
|
||||
if (existing) throw new Error("Category slug already exists");
|
||||
return await ctx.db.insert("categories", args);
|
||||
},
|
||||
});
|
||||
|
||||
export const update = mutation({
|
||||
args: {
|
||||
id: v.id("categories"),
|
||||
name: v.optional(v.string()),
|
||||
slug: v.optional(v.string()),
|
||||
description: v.optional(v.string()),
|
||||
topCategorySlug: v.optional(v.string()),
|
||||
seoTitle: v.optional(v.string()),
|
||||
seoDescription: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, { id, ...updates }) => {
|
||||
await Users.requireAdmin(ctx);
|
||||
await Categories.getCategoryOrThrow(ctx, id);
|
||||
const fields: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
if (value !== undefined) fields[key] = value;
|
||||
}
|
||||
if (Object.keys(fields).length > 0) {
|
||||
await ctx.db.patch(id, fields);
|
||||
}
|
||||
return id;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user