freshyo/packages/db_helper_sqlite/src/user-apis/product.ts
2026-05-11 19:25:44 +05:30

254 lines
7.7 KiB
TypeScript

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<UserProductDetailData | null> {
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<UserProductReview> {
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<ProductSummaryData[]> {
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<number[]> {
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<Date | null> {
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
}