338 lines
9.7 KiB
TypeScript
338 lines
9.7 KiB
TypeScript
// import redisClient from '@/src/lib/redis-client'
|
|
import {
|
|
getAllSlotsWithProductsForCache,
|
|
type SlotWithProductsData,
|
|
} from '@/src/dbService'
|
|
import { scaffoldAssetUrl } from '@/src/lib/s3-client'
|
|
import dayjs from 'dayjs'
|
|
|
|
// Define the structure for slot with products
|
|
interface SlotWithProducts {
|
|
id: number
|
|
deliveryTime: Date
|
|
freezeTime: Date
|
|
isActive: boolean
|
|
isCapacityFull: boolean
|
|
products: Array<{
|
|
id: number
|
|
name: string
|
|
shortDescription: string | null
|
|
productQuantity: number
|
|
price: string
|
|
marketPrice: string | null
|
|
unit: string | null
|
|
images: string[]
|
|
isOutOfStock: boolean
|
|
storeId: number | null
|
|
nextDeliveryDate: Date
|
|
}>
|
|
}
|
|
|
|
interface SlotInfo {
|
|
id: number
|
|
deliveryTime: Date
|
|
freezeTime: Date
|
|
isCapacityFull: boolean
|
|
}
|
|
|
|
async function transformSlotToStoreSlot(slot: SlotWithProductsData): Promise<SlotWithProducts> {
|
|
return {
|
|
id: slot.id,
|
|
deliveryTime: slot.deliveryTime,
|
|
freezeTime: slot.freezeTime,
|
|
isActive: slot.isActive,
|
|
isCapacityFull: slot.isCapacityFull,
|
|
products: slot.products.map((product) => ({
|
|
id: product.id,
|
|
name: product.name,
|
|
productQuantity: product.productQuantity,
|
|
shortDescription: product.shortDescription,
|
|
price: product.price.toString(),
|
|
marketPrice: product.marketPrice?.toString() || null,
|
|
unit: product.unit?.shortNotation || null,
|
|
images: scaffoldAssetUrl(
|
|
(product.images as string[]) || []
|
|
),
|
|
isOutOfStock: product.isOutOfStock,
|
|
storeId: product.storeId,
|
|
nextDeliveryDate: slot.deliveryTime,
|
|
})),
|
|
}
|
|
}
|
|
|
|
function extractSlotInfo(slot: SlotWithProductsData): SlotInfo {
|
|
return {
|
|
id: slot.id,
|
|
deliveryTime: slot.deliveryTime,
|
|
freezeTime: slot.freezeTime,
|
|
isCapacityFull: slot.isCapacityFull,
|
|
}
|
|
}
|
|
|
|
async function fetchAllTransformedSlots(): Promise<SlotWithProducts[]> {
|
|
const slots = await getAllSlotsWithProductsForCache()
|
|
return Promise.all(slots.map(transformSlotToStoreSlot))
|
|
}
|
|
|
|
export async function initializeSlotStore(): Promise<void> {
|
|
try {
|
|
console.log('Initializing slot store in Redis...')
|
|
|
|
// Fetch active delivery slots with future delivery times
|
|
const slots = await getAllSlotsWithProductsForCache()
|
|
|
|
/*
|
|
// Old implementation - direct DB queries:
|
|
import { db } from '@/src/db/db_index'
|
|
import { deliverySlotInfo } from '@/src/db/schema'
|
|
import { eq, gt, and, asc } from 'drizzle-orm'
|
|
|
|
const now = new Date();
|
|
const slots = await db.query.deliverySlotInfo.findMany({
|
|
where: and(
|
|
eq(deliverySlotInfo.isActive, true),
|
|
gt(deliverySlotInfo.deliveryTime, now),
|
|
),
|
|
with: {
|
|
productSlots: {
|
|
with: {
|
|
product: {
|
|
with: {
|
|
unit: true,
|
|
store: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
orderBy: asc(deliverySlotInfo.deliveryTime),
|
|
});
|
|
*/
|
|
|
|
// Transform data for storage
|
|
const slotsWithProducts = await Promise.all(
|
|
slots.map(async (slot) => ({
|
|
id: slot.id,
|
|
deliveryTime: slot.deliveryTime,
|
|
freezeTime: slot.freezeTime,
|
|
isActive: slot.isActive,
|
|
isCapacityFull: slot.isCapacityFull,
|
|
products: await Promise.all(
|
|
slot.products.map(async (product) => ({
|
|
id: product.id,
|
|
name: product.name,
|
|
productQuantity: product.productQuantity,
|
|
shortDescription: product.shortDescription,
|
|
price: product.price.toString(),
|
|
marketPrice: product.marketPrice?.toString() || null,
|
|
unit: product.unit?.shortNotation || null,
|
|
images: scaffoldAssetUrl(
|
|
(product.images as string[]) || []
|
|
),
|
|
isOutOfStock: product.isOutOfStock,
|
|
storeId: product.storeId,
|
|
nextDeliveryDate: slot.deliveryTime,
|
|
}))
|
|
),
|
|
}))
|
|
)
|
|
|
|
// Store each slot in Redis with key pattern "slot:{id}"
|
|
// for (const slot of slotsWithProducts) {
|
|
// 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,
|
|
isCapacityFull: slot.isCapacityFull,
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
export async function getSlotById(slotId: number): Promise<SlotWithProducts | null> {
|
|
try {
|
|
// const key = `slot:${slotId}`
|
|
// const data = await redisClient.get(key)
|
|
// if (!data) return null
|
|
// return JSON.parse(data) as SlotWithProducts
|
|
|
|
const slots = await getAllSlotsWithProductsForCache()
|
|
const slot = slots.find(s => s.id === slotId)
|
|
if (!slot) return null
|
|
|
|
return transformSlotToStoreSlot(slot)
|
|
} catch (error) {
|
|
console.error(`Error getting slot ${slotId}:`, error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function getAllSlots(): Promise<SlotWithProducts[]> {
|
|
try {
|
|
// Get all keys matching the pattern "slot:*"
|
|
// const keys = await redisClient.KEYS('slot:*')
|
|
//
|
|
// if (keys.length === 0) return []
|
|
//
|
|
// // Get all slots using MGET for better performance
|
|
// const slotsData = await redisClient.MGET(keys)
|
|
//
|
|
// const slots: SlotWithProducts[] = []
|
|
// for (const slotData of slotsData) {
|
|
// if (slotData) {
|
|
// slots.push(JSON.parse(slotData) as SlotWithProducts)
|
|
// }
|
|
// }
|
|
//
|
|
// return slots
|
|
|
|
return fetchAllTransformedSlots()
|
|
} catch (error) {
|
|
console.error('Error getting all slots:', error)
|
|
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[]
|
|
|
|
const slots = await getAllSlotsWithProductsForCache()
|
|
const productSlots: SlotInfo[] = []
|
|
|
|
for (const slot of slots) {
|
|
const hasProduct = slot.products.some(p => p.id === productId)
|
|
if (hasProduct) {
|
|
productSlots.push(extractSlotInfo(slot))
|
|
}
|
|
}
|
|
|
|
return productSlots
|
|
} 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
|
|
|
|
const slots = await getAllSlotsWithProductsForCache()
|
|
const result: Record<number, SlotInfo[]> = {}
|
|
|
|
for (const slot of slots) {
|
|
const slotInfo = extractSlotInfo(slot)
|
|
for (const product of slot.products) {
|
|
const productId = product.id
|
|
if (!result[productId]) {
|
|
result[productId] = []
|
|
}
|
|
result[productId].push(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) {
|
|
// const slots = JSON.parse(data) as SlotInfo[]
|
|
// // Filter out slots that are at full capacity
|
|
// result[productIds[i]] = slots.filter((slot) => !slot.isCapacityFull)
|
|
// }
|
|
// }
|
|
//
|
|
// return result
|
|
|
|
const slots = await getAllSlotsWithProductsForCache()
|
|
const productIdSet = new Set(productIds)
|
|
const result: Record<number, SlotInfo[]> = {}
|
|
|
|
for (const productId of productIds) {
|
|
result[productId] = []
|
|
}
|
|
|
|
for (const slot of slots) {
|
|
const slotInfo = extractSlotInfo(slot)
|
|
for (const product of slot.products) {
|
|
const pid = product.id
|
|
if (productIdSet.has(pid) && !slot.isCapacityFull) {
|
|
result[pid].push(slotInfo)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
} catch (error) {
|
|
console.error('Error getting products slots:', error)
|
|
return {}
|
|
}
|
|
}
|