import { router, publicProcedure } from '../trpc-index'; import { db } from '../../db/db_index'; import { productInfo, units, productSlots, deliverySlotInfo, storeInfo, productTags, productTagInfo } from '../../db/schema'; import { eq, gt, and, sql, inArray } from 'drizzle-orm'; import { generateSignedUrlsFromS3Urls, generateSignedUrlFromS3Url } from '../../lib/s3-client'; import { z } from 'zod'; import { getAllProducts as getAllProductsFromCache } from '../../stores/product-store'; import { getDashboardTags as getDashboardTagsFromCache } from '../../stores/product-tag-store'; import Fuse from 'fuse.js'; export const getNextDeliveryDate = async (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), gt(deliverySlotInfo.deliveryTime, sql`NOW()`) ) ) .orderBy(deliverySlotInfo.deliveryTime) .limit(1); return result[0]?.deliveryTime || null; }; export const commonRouter = router({ getDashboardTags: publicProcedure .query(async () => { // Get dashboard tags from cache const tags = await getDashboardTagsFromCache(); return { tags: tags, }; }), getAllProductsSummary: publicProcedure .input(z.object({ searchQuery: z.string().optional(), tagId: z.number().optional() })) .query(async ({ input }) => { const { searchQuery, tagId } = input; // Get all products from cache let products = await getAllProductsFromCache(); products = products.filter(item => Boolean(item.id)) // Apply tag filtering if tagId is provided if (tagId) { // Get products that have this tag from the database const taggedProducts = await db .select({ productId: productTags.productId }) .from(productTags) .where(eq(productTags.tagId, tagId)); const taggedProductIds = new Set(taggedProducts.map(tp => tp.productId)); // Filter products based on tag products = products.filter(product => taggedProductIds.has(product.id)); } // Apply search filtering if searchQuery is provided using Fuse.js if (searchQuery) { const fuse = new Fuse(products, { keys: [ 'name', 'shortDescription', 'longDescription', 'store.name', // Search in store name too 'productTags', // Search in product tags too ], threshold: 0.3, // Adjust fuzziness (0.0 = exact match, 1.0 = match anything) includeScore: true, shouldSort: true, }); const fuseResults = fuse.search(searchQuery); products = fuseResults.map(result => result.item); } // Get suspended product IDs to filter them out const suspendedProducts = await db .select({ id: productInfo.id }) .from(productInfo) .where(eq(productInfo.isSuspended, true)); const suspendedProductIds = new Set(suspendedProducts.map(sp => sp.id)); // Filter out suspended products products = products.filter(product => !suspendedProductIds.has(product.id)); // Format products to match the expected response structure const formattedProducts = await Promise.all( products.map(async (product) => { const nextDeliveryDate = await getNextDeliveryDate(product.id); return { id: product.id, name: product.name, shortDescription: product.shortDescription, price: parseFloat(product.price), marketPrice: product.marketPrice ? parseFloat(product.marketPrice) : null, unit: product.unitNotation, unitNotation: product.unitNotation, incrementStep: product.incrementStep, productQuantity: product.productQuantity, storeId: product.store?.id || null, isOutOfStock: product.isOutOfStock, nextDeliveryDate: nextDeliveryDate ? nextDeliveryDate.toISOString() : null, images: product.images, // Already signed URLs from cache }; }) ); return { products: formattedProducts, count: formattedProducts.length, }; }), getStoresSummary: publicProcedure .query(async () => { const stores = await db.query.storeInfo.findMany({ columns: { id: true, name: true, description: true, }, }); return { stores, }; }), healthCheck: publicProcedure .query(async () => { // Test DB connection by selecting product names await db.select({ name: productInfo.name }).from(productInfo).limit(1); return { status: "ok", }; }), });