154 lines
No EOL
4.9 KiB
TypeScript
154 lines
No EOL
4.9 KiB
TypeScript
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<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),
|
|
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",
|
|
};
|
|
}),
|
|
}); |