import { db } from '../db/db_index' import { deliverySlotInfo, productInfo, productReviews, productSlots, productTags, specialDeals, storeInfo, units, users } from '../db/schema' import { and, desc, eq, gt, inArray, sql } from 'drizzle-orm' import type { UserProductDetailData, UserProductReview } from '@packages/shared' const getStringArray = (value: unknown): string[] | null => { if (!Array.isArray(value)) return null return value.map((item) => String(item)) } export async function getProductDetailById(productId: number): Promise { const productData = await db .select({ id: productInfo.id, name: productInfo.name, shortDescription: productInfo.shortDescription, longDescription: productInfo.longDescription, price: productInfo.price, marketPrice: productInfo.marketPrice, images: productInfo.images, isOutOfStock: productInfo.isOutOfStock, storeId: productInfo.storeId, unitShortNotation: units.shortNotation, incrementStep: productInfo.incrementStep, productQuantity: productInfo.productQuantity, isFlashAvailable: productInfo.isFlashAvailable, flashPrice: productInfo.flashPrice, }) .from(productInfo) .innerJoin(units, eq(productInfo.unitId, units.id)) .where(eq(productInfo.id, productId)) .limit(1) if (productData.length === 0) { return null } const product = productData[0] const storeData = product.storeId ? await db.query.storeInfo.findFirst({ where: eq(storeInfo.id, product.storeId), columns: { id: true, name: true, description: true }, }) : null // Note: deliverySlots are now fetched from cache in the frontend via useSlots() // This avoids expensive database joins on every product detail view const specialDealsData = await db .select({ quantity: specialDeals.quantity, price: specialDeals.price, validTill: specialDeals.validTill, }) .from(specialDeals) .where( and( eq(specialDeals.productId, productId), gt(specialDeals.validTill, sql`CURRENT_TIMESTAMP`) ) ) .orderBy(specialDeals.quantity) return { id: product.id, name: product.name, shortDescription: product.shortDescription ?? null, longDescription: product.longDescription ?? null, price: String(product.price ?? '0'), marketPrice: product.marketPrice ? String(product.marketPrice) : null, unitNotation: product.unitShortNotation, images: getStringArray(product.images), isOutOfStock: product.isOutOfStock, store: storeData ? { id: storeData.id, name: storeData.name, description: storeData.description ?? null, } : null, incrementStep: product.incrementStep, productQuantity: product.productQuantity, isFlashAvailable: product.isFlashAvailable, flashPrice: product.flashPrice?.toString() || null, deliverySlots: [], // Fetched from cache in frontend via useSlots() specialDeals: specialDealsData.map((deal) => ({ quantity: String(deal.quantity ?? '0'), price: String(deal.price ?? '0'), validTill: deal.validTill, })), } } export async function getProductReviews(productId: number, limit: number, offset: number) { const reviews = await db .select({ id: productReviews.id, reviewBody: productReviews.reviewBody, ratings: productReviews.ratings, imageUrls: productReviews.imageUrls, reviewTime: productReviews.reviewTime, userName: users.name, }) .from(productReviews) .innerJoin(users, eq(productReviews.userId, users.id)) .where(eq(productReviews.productId, productId)) .orderBy(desc(productReviews.reviewTime)) .limit(limit) .offset(offset) const totalCountResult = await db .select({ count: sql`count(*)` }) .from(productReviews) .where(eq(productReviews.productId, productId)) const totalCount = Number(totalCountResult[0].count) const mappedReviews: UserProductReview[] = reviews.map((review) => ({ id: review.id, reviewBody: review.reviewBody, ratings: review.ratings, imageUrls: getStringArray(review.imageUrls), reviewTime: review.reviewTime, userName: review.userName ?? null, })) return { reviews: mappedReviews, totalCount, } } export async function getProductById(productId: number) { return db.query.productInfo.findFirst({ where: eq(productInfo.id, productId), }) } export async function createProductReview( userId: number, productId: number, reviewBody: string, ratings: number, imageUrls: string[] ): Promise { const [newReview] = await db.insert(productReviews).values({ userId, productId, reviewBody, ratings, imageUrls, }).returning({ id: productReviews.id, reviewBody: productReviews.reviewBody, ratings: productReviews.ratings, imageUrls: productReviews.imageUrls, reviewTime: productReviews.reviewTime, }) return { id: newReview.id, reviewBody: newReview.reviewBody, ratings: newReview.ratings, imageUrls: getStringArray(newReview.imageUrls), reviewTime: newReview.reviewTime, userName: null, } } export interface ProductSummaryData { id: number name: string shortDescription: string | null price: string marketPrice: string | null images: unknown isOutOfStock: boolean unitShortNotation: string productQuantity: number } export async function getAllProductsWithUnits(tagId?: number): Promise { let productIds: number[] | null = null // If tagId is provided, get products that have this tag if (tagId) { const taggedProducts = await db .select({ productId: productTags.productId }) .from(productTags) .where(eq(productTags.tagId, tagId)) productIds = taggedProducts.map(tp => tp.productId) } let whereCondition = undefined // Filter by product IDs if tag filtering is applied if (productIds && productIds.length > 0) { whereCondition = inArray(productInfo.id, productIds) } const results = await db .select({ id: productInfo.id, name: productInfo.name, shortDescription: productInfo.shortDescription, price: productInfo.price, marketPrice: productInfo.marketPrice, images: productInfo.images, isOutOfStock: productInfo.isOutOfStock, unitShortNotation: units.shortNotation, productQuantity: productInfo.productQuantity, }) .from(productInfo) .innerJoin(units, eq(productInfo.unitId, units.id)) .where(whereCondition) return results.map((product) => ({ ...product, price: String(product.price ?? '0'), marketPrice: product.marketPrice ? String(product.marketPrice) : null, })) } /** * Get all suspended product IDs */ export async function getSuspendedProductIds(): Promise { const suspendedProducts = await db .select({ id: productInfo.id }) .from(productInfo) .where(eq(productInfo.isSuspended, true)) return suspendedProducts.map(sp => sp.id) } /** * Get next delivery date for a product (with capacity check) * This version filters by both isActive AND isCapacityFull */ export async function getNextDeliveryDateWithCapacity(productId: number): Promise { const result = await db .select({ deliveryTime: deliverySlotInfo.deliveryTime }) .from(productSlots) .innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id)) .where( and( eq(productSlots.productId, productId), eq(deliverySlotInfo.isActive, true), eq(deliverySlotInfo.isCapacityFull, false), gt(deliverySlotInfo.deliveryTime, sql`CURRENT_TIMESTAMP`) ) ) .orderBy(deliverySlotInfo.deliveryTime) .limit(1) return result[0]?.deliveryTime || null }