diff --git a/development_guidelines.md b/development_guidelines.md new file mode 100644 index 0000000..228865f --- /dev/null +++ b/development_guidelines.md @@ -0,0 +1 @@ +I acknowledge and will strictly adhere to the caching strategy constraints outlined. My approach will exclusively use the `use cache` directive, prioritize multi-tenancy security by passing `tenantId` as an argument, implement granular `cacheTag` invalidation, recommend appropriate cache lifetimes, and clearly distinguish between static and dynamic components using Suspense. diff --git a/src/db/actions/dashboard/products/actions.ts b/src/db/actions/dashboard/products/actions.ts index f0ccedd..a7e77dd 100644 --- a/src/db/actions/dashboard/products/actions.ts +++ b/src/db/actions/dashboard/products/actions.ts @@ -11,21 +11,15 @@ import { dashboardActionClient } from "@/lib/safe-action-clients/dashboard-clien import { revalidatePath } from "next/cache"; import { checkPermission } from "@/lib/auth/check-permission"; +import { + getCachedDashboardProduct, + getCachedDashboardProducts, +} from "./cached-actions"; + export const getDashboardProducts = dashboardActionClient.action( async ({ ctx }) => { checkPermission(ctx, "products:read"); - - const storeId = ctx.storeId; - - const products = await db.query.ProductTable.findMany({ - where: eq(ProductTable.store_id, storeId), - columns: { - store_id: false, - }, - orderBy: [desc(ProductTable.updated_at)], - }); - - return products; + return await getCachedDashboardProducts(ctx.storeId); }, ); @@ -35,17 +29,7 @@ export const getDashboardProduct = dashboardActionClient checkPermission(ctx, "products:read"); const slug = parsedInput.slug; - const product = await db.query.ProductTable.findFirst({ - where: and( - eq(ProductTable.slug, slug), - eq(ProductTable.store_id, ctx.storeId), - ), - columns: { - store_id: false, - }, - }); - - return product; + return await getCachedDashboardProduct(ctx.storeId, slug); }); export const updateDashboardProduct = dashboardActionClient @@ -73,6 +57,7 @@ export const updateDashboardProduct = dashboardActionClient ), ) .returning({ id: ProductTable.id }); + revalidatePath("/products"); return results[0]; }); @@ -119,7 +104,7 @@ export const createDashboardProduct = dashboardActionClient if (!product[0].id) { return { success: false, error: "Product not created." }; } - + revalidatePath("/products"); return { success: true, message: "Product created" }; }); @@ -146,6 +131,6 @@ export const updateDashboardProductDetails = dashboardActionClient eq(ProductTable.store_id, ctx.storeId), ), ); - + revalidatePath("/products"); return { success: true, message: "Product updated" }; }); diff --git a/src/db/actions/dashboard/products/cached-actions.ts b/src/db/actions/dashboard/products/cached-actions.ts new file mode 100644 index 0000000..124af06 --- /dev/null +++ b/src/db/actions/dashboard/products/cached-actions.ts @@ -0,0 +1,34 @@ +"use cache"; + +import { db } from "@/db/db"; +import { ProductTable } from "@/db/schema"; +import { and, desc, eq } from "drizzle-orm"; +import { cacheTag } from "next/cache"; + +export async function getCachedDashboardProducts(storeId: string) { + cacheTag(`tenant-${storeId}:products`); + + const products = await db.query.ProductTable.findMany({ + where: eq(ProductTable.store_id, storeId), + columns: { + store_id: false, + }, + orderBy: [desc(ProductTable.updated_at)], + }); + + return products; +} + +export async function getCachedDashboardProduct(storeId: string, slug: string) { + cacheTag(`tenant-${storeId}:products`); + cacheTag(`tenant-${storeId}:product-${slug}`); + + const product = await db.query.ProductTable.findFirst({ + where: and(eq(ProductTable.slug, slug), eq(ProductTable.store_id, storeId)), + columns: { + store_id: false, + }, + }); + + return product; +}