import { cookies } from "next/headers";import { verifyToken } from "./session";import { categories, products, subcategories, subcollections, users,} from "@/db/schema";import { db } from "@/db";import { eq, and, count } from "drizzle-orm";import { unstable_cache } from "./unstable-cache";import { sql } from "drizzle-orm"; export async function getUser() { const sessionCookie = (await cookies()).get("session"); if (!sessionCookie || !sessionCookie.value) { return null; } const sessionData = await verifyToken(sessionCookie.value); if ( !sessionData || !sessionData.user || typeof sessionData.user.id !== "number" ) { return null; } if (new Date(sessionData.expires) < new Date()) { return null; } const user = await db .select() .from(users) .where(and(eq(users.id, sessionData.user.id))) .limit(1); if (user.length === 0) { return null; } return user[0];} export const getProductsForSubcategory = unstable_cache( (subcategorySlug: string) => db.query.products.findMany({ where: (products, { eq, and }) => and(eq(products.subcategory_slug, subcategorySlug)), orderBy: (products, { asc }) => asc(products.slug), }), ["subcategory-products"], { revalidate: 60 * 60 * 2, // two hours, },); export const getCollections = unstable_cache( () => db.query.collections.findMany({ with: { categories: true, }, orderBy: (collections, { asc }) => asc(collections.name), }), ["collections"], { revalidate: 60 * 60 * 2, // two hours, },); export const getProductDetails = unstable_cache( (productSlug: string) => db.query.products.findFirst({ where: (products, { eq }) => eq(products.slug, productSlug), }), ["product"], { revalidate: 60 * 60 * 2, // two hours, },); export const getSubcategory = unstable_cache( (subcategorySlug: string) => db.query.subcategories.findFirst({ where: (subcategories, { eq }) => eq(subcategories.slug, subcategorySlug), }), ["subcategory"], { revalidate: 60 * 60 * 2, // two hours, },); export const getCategory = unstable_cache( (categorySlug: string) => db.query.categories.findFirst({ where: (categories, { eq }) => eq(categories.slug, categorySlug), with: { subcollections: { with: { subcategories: true, }, }, }, }), ["category"], { revalidate: 60 * 60 * 2, // two hours, },); export const getCollectionDetails = unstable_cache( async (collectionSlug: string) => db.query.collections.findMany({ with: { categories: true, }, where: (collections, { eq }) => eq(collections.slug, collectionSlug), orderBy: (collections, { asc }) => asc(collections.slug), }), ["collection"], { revalidate: 60 * 60 * 2, // two hours, },); export const getProductCount = unstable_cache( () => db.select({ count: count() }).from(products), ["total-product-count"], { revalidate: 60 * 60 * 2, // two hours, },); // could be optimized by storing category slug on the products tableexport const getCategoryProductCount = unstable_cache( (categorySlug: string) => db .select({ count: count() }) .from(categories) .leftJoin( subcollections, eq(categories.slug, subcollections.category_slug), ) .leftJoin( subcategories, eq(subcollections.id, subcategories.subcollection_id), ) .leftJoin(products, eq(subcategories.slug, products.subcategory_slug)) .where(eq(categories.slug, categorySlug)), ["category-product-count"], { revalidate: 60 * 60 * 2, // two hours, },); export const getSubcategoryProductCount = unstable_cache( (subcategorySlug: string) => db .select({ count: count() }) .from(products) .where(eq(products.subcategory_slug, subcategorySlug)), ["subcategory-product-count"], { revalidate: 60 * 60 * 2, // two hours, },); export const getSearchResults = unstable_cache( async (searchTerm: string) => { let results; // do we really need to do this hybrid search pattern? if (searchTerm.length <= 2) { // If the search term is short (e.g., "W"), use ILIKE for prefix matching results = await db .select() .from(products) .where(sql`${products.name} ILIKE ${searchTerm + "%"}`) // Prefix match .limit(5) .innerJoin( subcategories, sql`${products.subcategory_slug} = ${subcategories.slug}`, ) .innerJoin( subcollections, sql`${subcategories.subcollection_id} = ${subcollections.id}`, ) .innerJoin( categories, sql`${subcollections.category_slug} = ${categories.slug}`, ); } else { // For longer search terms, use full-text search with tsquery const formattedSearchTerm = searchTerm .split(" ") .filter((term) => term.trim() !== "") // Filter out empty terms .map((term) => `${term}:*`) .join(" & "); results = await db .select() .from(products) .where( sql`to_tsvector('english', ${products.name}) @@ to_tsquery('english', ${formattedSearchTerm})`, ) .limit(5) .innerJoin( subcategories, sql`${products.subcategory_slug} = ${subcategories.slug}`, ) .innerJoin( subcollections, sql`${subcategories.subcollection_id} = ${subcollections.id}`, ) .innerJoin( categories, sql`${subcollections.category_slug} = ${categories.slug}`, ); } return results; }, ["search-results"], { revalidate: 60 * 60 * 2 }, // two hours);
import { cookies } from "next/headers";
import { verifyToken } from "./session";
import {
categories,
products,
subcategories,
subcollections,
users,
} from "@/db/schema";
import { db } from "@/db";
import { eq, and, count } from "drizzle-orm";
import { unstable_cache } from "./unstable-cache";
import { sql } from "drizzle-orm";
export async function getUser() {
const sessionCookie = (await cookies()).get("session");
if (!sessionCookie || !sessionCookie.value) {
return null;
}
const sessionData = await verifyToken(sessionCookie.value);
if (
!sessionData ||
!sessionData.user ||
typeof sessionData.user.id !== "number"
) {
return null;
}
if (new Date(sessionData.expires) < new Date()) {
return null;
}
const user = await db
.select()
.from(users)
.where(and(eq(users.id, sessionData.user.id)))
.limit(1);
if (user.length === 0) {
return null;
}
return user[0];
}
export const getProductsForSubcategory = unstable_cache(
(subcategorySlug: string) =>
db.query.products.findMany({
where: (products, { eq, and }) =>
and(eq(products.subcategory_slug, subcategorySlug)),
orderBy: (products, { asc }) => asc(products.slug),
}),
["subcategory-products"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
export const getCollections = unstable_cache(
() =>
db.query.collections.findMany({
with: {
categories: true,
},
orderBy: (collections, { asc }) => asc(collections.name),
}),
["collections"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
export const getProductDetails = unstable_cache(
(productSlug: string) =>
db.query.products.findFirst({
where: (products, { eq }) => eq(products.slug, productSlug),
}),
["product"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
export const getSubcategory = unstable_cache(
(subcategorySlug: string) =>
db.query.subcategories.findFirst({
where: (subcategories, { eq }) => eq(subcategories.slug, subcategorySlug),
}),
["subcategory"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
export const getCategory = unstable_cache(
(categorySlug: string) =>
db.query.categories.findFirst({
where: (categories, { eq }) => eq(categories.slug, categorySlug),
with: {
subcollections: {
with: {
subcategories: true,
},
},
},
}),
["category"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
export const getCollectionDetails = unstable_cache(
async (collectionSlug: string) =>
db.query.collections.findMany({
with: {
categories: true,
},
where: (collections, { eq }) => eq(collections.slug, collectionSlug),
orderBy: (collections, { asc }) => asc(collections.slug),
}),
["collection"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
export const getProductCount = unstable_cache(
() => db.select({ count: count() }).from(products),
["total-product-count"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
// could be optimized by storing category slug on the products table
export const getCategoryProductCount = unstable_cache(
(categorySlug: string) =>
db
.select({ count: count() })
.from(categories)
.leftJoin(
subcollections,
eq(categories.slug, subcollections.category_slug),
)
.leftJoin(
subcategories,
eq(subcollections.id, subcategories.subcollection_id),
)
.leftJoin(products, eq(subcategories.slug, products.subcategory_slug))
.where(eq(categories.slug, categorySlug)),
["category-product-count"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
export const getSubcategoryProductCount = unstable_cache(
(subcategorySlug: string) =>
db
.select({ count: count() })
.from(products)
.where(eq(products.subcategory_slug, subcategorySlug)),
["subcategory-product-count"],
{
revalidate: 60 * 60 * 2, // two hours,
},
);
export const getSearchResults = unstable_cache(
async (searchTerm: string) => {
let results;
// do we really need to do this hybrid search pattern?
if (searchTerm.length <= 2) {
// If the search term is short (e.g., "W"), use ILIKE for prefix matching
results = await db
.select()
.from(products)
.where(sql`${products.name} ILIKE ${searchTerm + "%"}`) // Prefix match
.limit(5)
.innerJoin(
subcategories,
sql`${products.subcategory_slug} = ${subcategories.slug}`,
)
.innerJoin(
subcollections,
sql`${subcategories.subcollection_id} = ${subcollections.id}`,
)
.innerJoin(
categories,
sql`${subcollections.category_slug} = ${categories.slug}`,
);
} else {
// For longer search terms, use full-text search with tsquery
const formattedSearchTerm = searchTerm
.split(" ")
.filter((term) => term.trim() !== "") // Filter out empty terms
.map((term) => `${term}:*`)
.join(" & ");
results = await db
.select()
.from(products)
.where(
sql`to_tsvector('english', ${products.name}) @@ to_tsquery('english', ${formattedSearchTerm})`,
)
.limit(5)
.innerJoin(
subcategories,
sql`${products.subcategory_slug} = ${subcategories.slug}`,
)
.innerJoin(
subcollections,
sql`${subcategories.subcollection_id} = ${subcollections.id}`,
)
.innerJoin(
categories,
sql`${subcollections.category_slug} = ${categories.slug}`,
);
}
return results;
},
["search-results"],
{ revalidate: 60 * 60 * 2 }, // two hours
);