This commit is contained in:
shafi54 2026-01-29 01:31:53 +05:30
parent 46f5fa180c
commit 2929e7725a
3 changed files with 143 additions and 63 deletions

View file

@ -24,6 +24,12 @@ interface SlotWithProducts {
}>;
}
interface SlotInfo {
id: number;
deliveryTime: Date;
freezeTime: Date;
}
export async function initializeSlotStore(): Promise<void> {
try {
console.log('Initializing slot store in Redis...');
@ -82,6 +88,28 @@ export async function initializeSlotStore(): Promise<void> {
await redisClient.set(`slot:${slot.id}`, JSON.stringify(slot));
}
// Build and store product-slots map
// Group slots by productId
const productSlotsMap: Record<number, SlotInfo[]> = {};
for (const slot of slotsWithProducts) {
for (const product of slot.products) {
if (!productSlotsMap[product.id]) {
productSlotsMap[product.id] = [];
}
productSlotsMap[product.id].push({
id: slot.id,
deliveryTime: slot.deliveryTime,
freezeTime: slot.freezeTime,
});
}
}
// Store each product's slots in Redis with key pattern "product:{id}:slots"
for (const [productId, slotInfos] of Object.entries(productSlotsMap)) {
await redisClient.set(`product:${productId}:slots`, JSON.stringify(slotInfos));
}
console.log('Slot store initialized successfully');
} catch (error) {
console.error('Error initializing slot store:', error);
@ -123,3 +151,70 @@ export async function getAllSlots(): Promise<SlotWithProducts[]> {
return [];
}
}
export async function getProductSlots(productId: number): Promise<SlotInfo[]> {
try {
const key = `product:${productId}:slots`;
const data = await redisClient.get(key);
if (!data) return [];
return JSON.parse(data) as SlotInfo[];
} catch (error) {
console.error(`Error getting slots for product ${productId}:`, error);
return [];
}
}
export async function getAllProductsSlots(): Promise<Record<number, SlotInfo[]>> {
try {
// Get all keys matching the pattern "product:*:slots"
const keys = await redisClient.KEYS('product:*:slots');
if (keys.length === 0) return {};
// Get all product slots using MGET for better performance
const productsData = await redisClient.MGET(keys);
const result: Record<number, SlotInfo[]> = {};
for (const key of keys) {
// Extract productId from key "product:{id}:slots"
const match = key.match(/product:(\d+):slots/);
if (match) {
const productId = parseInt(match[1], 10);
const dataIndex = keys.indexOf(key);
if (productsData[dataIndex]) {
result[productId] = JSON.parse(productsData[dataIndex]) as SlotInfo[];
}
}
}
return result;
} catch (error) {
console.error('Error getting all products slots:', error);
return {};
}
}
export async function getMultipleProductsSlots(productIds: number[]): Promise<Record<number, SlotInfo[]>> {
try {
if (productIds.length === 0) return {};
// Build keys for all productIds
const keys = productIds.map(id => `product:${id}:slots`);
// Use MGET for batch retrieval
const productsData = await redisClient.MGET(keys);
const result: Record<number, SlotInfo[]> = {};
for (let i = 0; i < productIds.length; i++) {
const data = productsData[i];
if (data) {
result[productIds[i]] = JSON.parse(data) as SlotInfo[];
}
}
return result;
} catch (error) {
console.error('Error getting products slots:', error);
return {};
}
}

View file

@ -5,6 +5,7 @@ import { cartItems, productInfo, units, productSlots, deliverySlotInfo } from '.
import { eq, and, sql, inArray, gt } from 'drizzle-orm';
import { ApiError } from '../../lib/api-error';
import { generateSignedUrlsFromS3Urls } from '../../lib/s3-client';
import { getProductSlots, getMultipleProductsSlots } from '../../stores/slot-store';
interface CartResponse {
items: any[];
@ -181,6 +182,52 @@ export const cartRouter = router({
};
}),
// Original DB-based getCartSlots (commented out)
// getCartSlots: publicProcedure
// .input(z.object({
// productIds: z.array(z.number().int().positive())
// }))
// .query(async ({ input }) => {
// const { productIds } = input;
//
// if (productIds.length === 0) {
// return {};
// }
//
// // Get slots for these products where freeze time is after current time
// const slotsData = await db
// .select({
// productId: productSlots.productId,
// slotId: deliverySlotInfo.id,
// deliveryTime: deliverySlotInfo.deliveryTime,
// freezeTime: deliverySlotInfo.freezeTime,
// isActive: deliverySlotInfo.isActive,
// })
// .from(productSlots)
// .innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id))
// .where(and(
// inArray(productSlots.productId, productIds),
// gt(deliverySlotInfo.freezeTime, sql`NOW()`),
// eq(deliverySlotInfo.isActive, true)
// ));
//
// // Group by productId
// const result: Record<number, any[]> = {};
// slotsData.forEach(slot => {
// if (!result[slot.productId]) {
// result[slot.productId] = [];
// }
// result[slot.productId].push({
// id: slot.slotId,
// deliveryTime: slot.deliveryTime,
// freezeTime: slot.freezeTime,
// });
// });
//
// return result;
// }),
// Cache-based getCartSlots
getCartSlots: publicProcedure
.input(z.object({
productIds: z.array(z.number().int().positive())
@ -192,36 +239,6 @@ export const cartRouter = router({
return {};
}
// Get slots for these products where freeze time is after current time
const slotsData = await db
.select({
productId: productSlots.productId,
slotId: deliverySlotInfo.id,
deliveryTime: deliverySlotInfo.deliveryTime,
freezeTime: deliverySlotInfo.freezeTime,
isActive: deliverySlotInfo.isActive,
})
.from(productSlots)
.innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id))
.where(and(
inArray(productSlots.productId, productIds),
gt(deliverySlotInfo.freezeTime, sql`NOW()`),
eq(deliverySlotInfo.isActive, true)
));
// Group by productId
const result: Record<number, any[]> = {};
slotsData.forEach(slot => {
if (!result[slot.productId]) {
result[slot.productId] = [];
}
result[slot.productId].push({
id: slot.slotId,
deliveryTime: slot.deliveryTime,
freezeTime: slot.freezeTime,
});
});
return result;
return await getMultipleProductsSlots(productIds);
}),
});

View file

@ -161,37 +161,6 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
const router = useRouter();
// Process slots: flatten and unique
const availableSlots = React.useMemo(() => {
if (!slotsData) return [];
const allSlots = Object.values(slotsData).flat();
const uniqueSlots = allSlots.filter(
(slot, index, self) => index === self.findIndex((s) => s.id === slot.id)
);
// Smart time window formatting function
const formatTimeRange = (deliveryTime: string) => {
const time = dayjs(deliveryTime);
const endTime = time.add(1, 'hour');
const startPeriod = time.format('A');
const endPeriod = endTime.format('A');
let timeRange;
if (startPeriod === endPeriod) {
timeRange = `${time.format('h')}-${endTime.format('h')} ${startPeriod}`;
} else {
timeRange = `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`;
}
return `${time.format('ddd, DD MMM ')}${timeRange}`;
};
return uniqueSlots.map((slot) => ({
label: `Delivery: ${formatTimeRange(slot.deliveryTime)} - Close time: ${dayjs(slot.freezeTime).format("h:mm a")}`,
value: slot.id,
}));
}, [slotsData]);
// Get available slots for a specific product
const getAvailableSlotsForProduct = React.useMemo(() => {
return (productId: number) => {
@ -330,7 +299,6 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
);
if (upcomingSlots.length > 0) {
console.log({upcomingSlots, existingSlotId, cartSlotIds})
if (existingSlotId) {
const slotStillValid = upcomingSlots.some(slot => slot.id === existingSlotId);