enh
This commit is contained in:
parent
68103010c6
commit
639428caba
21 changed files with 19950 additions and 403 deletions
|
|
@ -10,7 +10,8 @@ import initFunc from '@/src/lib/init';
|
|||
import { createExpressMiddleware } from '@trpc/server/adapters/express';
|
||||
import { appRouter } from '@/src/trpc/router';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { jwtVerify } from 'jose'
|
||||
import { encodedJwtSecret } from '@/src/lib/env-exporter';
|
||||
import signedUrlCache from '@/src/lib/signed-url-cache';
|
||||
import { seed } from '@/src/lib/seed';
|
||||
import '@/src/jobs/jobs-index';
|
||||
|
|
@ -75,7 +76,8 @@ app.use('/api/trpc', createExpressMiddleware({
|
|||
if (authHeader?.startsWith('Bearer ')) {
|
||||
const token = authHeader.substring(7);
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key') as any;
|
||||
const { payload } = await jwtVerify(token, encodedJwtSecret);
|
||||
const decoded = payload as any;
|
||||
|
||||
// Check if this is a staff token (has staffId)
|
||||
if (decoded.staffId) {
|
||||
|
|
|
|||
|
|
@ -24,19 +24,15 @@
|
|||
"@turf/turf": "^7.2.0",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"axios": "^1.11.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bullmq": "^5.63.0",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.11.18",
|
||||
"dotenv": "^17.2.1",
|
||||
"expo-server-sdk": "^4.0.0",
|
||||
"express": "^5.1.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"node-cron": "^4.2.1",
|
||||
"redis": "^5.9.0",
|
||||
"jose": "^6.2.2",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -44,8 +40,8 @@
|
|||
"@types/node": "^24.5.2",
|
||||
"rimraf": "^6.1.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsx": "^4.20.5",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import * as cron from 'node-cron';
|
||||
import { checkPendingPayments, checkRefundStatuses } from '@/src/jobs/payment-status-checker'
|
||||
|
||||
const runCombinedJob = async () => {
|
||||
|
|
@ -25,4 +24,4 @@ const runCombinedJob = async () => {
|
|||
runCombinedJob();
|
||||
|
||||
// Schedule combined cron job every 10 minutes
|
||||
cron.schedule('*/10 * * * *', runCombinedJob);
|
||||
// cron.schedule('*/10 * * * *', runCombinedJob);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import * as cron from 'node-cron';
|
||||
|
||||
interface PendingPaymentRecord {
|
||||
payment: any;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import * as cron from 'node-cron';
|
||||
// import * as cron from 'node-cron';
|
||||
const cron:any = {}
|
||||
import { toggleFlashDeliveryForItems, toggleKeyVal } from '@/src/dbService';
|
||||
import { CONST_KEYS } from '@/src/lib/const-keys'
|
||||
import { computeConstants } from '@/src/lib/const-store'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { getAllKeyValStore } from '@/src/dbService'
|
||||
import redisClient from '@/src/lib/redis-client'
|
||||
// import redisClient from '@/src/lib/redis-client'
|
||||
import { CONST_KEYS, CONST_KEYS_ARRAY, type ConstKey } from '@/src/lib/const-keys'
|
||||
|
||||
const CONST_REDIS_PREFIX = 'const:';
|
||||
// const CONST_REDIS_PREFIX = 'const:';
|
||||
|
||||
export const computeConstants = async (): Promise<void> => {
|
||||
try {
|
||||
|
|
@ -18,15 +18,13 @@ export const computeConstants = async (): Promise<void> => {
|
|||
|
||||
const constants = await getAllKeyValStore();
|
||||
|
||||
for (const constant of constants) {
|
||||
const redisKey = `${CONST_REDIS_PREFIX}${constant.key}`;
|
||||
const value = JSON.stringify(constant.value);
|
||||
// console.log({redisKey, value})
|
||||
// for (const constant of constants) {
|
||||
// const redisKey = `${CONST_REDIS_PREFIX}${constant.key}`;
|
||||
// const value = JSON.stringify(constant.value);
|
||||
// await redisClient.set(redisKey, value);
|
||||
// }
|
||||
|
||||
await redisClient.set(redisKey, value);
|
||||
}
|
||||
|
||||
console.log(`Computed and stored ${constants.length} constants in Redis`);
|
||||
console.log(`Computed ${constants.length} constants from DB`);
|
||||
} catch (error) {
|
||||
console.error('Failed to compute constants:', error);
|
||||
throw error;
|
||||
|
|
@ -34,46 +32,76 @@ export const computeConstants = async (): Promise<void> => {
|
|||
};
|
||||
|
||||
export const getConstant = async <T = any>(key: string): Promise<T | null> => {
|
||||
const redisKey = `${CONST_REDIS_PREFIX}${key}`;
|
||||
const value = await redisClient.get(redisKey);
|
||||
// const redisKey = `${CONST_REDIS_PREFIX}${key}`;
|
||||
// const value = await redisClient.get(redisKey);
|
||||
//
|
||||
// if (!value) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// return JSON.parse(value) as T;
|
||||
// } catch {
|
||||
// return value as unknown as T;
|
||||
// }
|
||||
|
||||
if (!value) {
|
||||
const constants = await getAllKeyValStore();
|
||||
const entry = constants.find(c => c.key === key);
|
||||
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value) as T;
|
||||
} catch {
|
||||
return value as unknown as T;
|
||||
}
|
||||
return entry.value as T;
|
||||
};
|
||||
|
||||
export const getConstants = async <T = any>(keys: string[]): Promise<Record<string, T | null>> => {
|
||||
const redisKeys = keys.map(key => `${CONST_REDIS_PREFIX}${key}`);
|
||||
const values = await redisClient.MGET(redisKeys);
|
||||
// const redisKeys = keys.map(key => `${CONST_REDIS_PREFIX}${key}`);
|
||||
// const values = await redisClient.MGET(redisKeys);
|
||||
//
|
||||
// const result: Record<string, T | null> = {};
|
||||
// keys.forEach((key, index) => {
|
||||
// const value = values[index];
|
||||
// if (!value) {
|
||||
// result[key] = null;
|
||||
// } else {
|
||||
// try {
|
||||
// result[key] = JSON.parse(value) as T;
|
||||
// } catch {
|
||||
// result[key] = value as unknown as T;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return result;
|
||||
|
||||
const constants = await getAllKeyValStore();
|
||||
const constantsMap = new Map(constants.map(c => [c.key, c.value]));
|
||||
|
||||
const result: Record<string, T | null> = {};
|
||||
keys.forEach((key, index) => {
|
||||
const value = values[index];
|
||||
if (!value) {
|
||||
result[key] = null;
|
||||
} else {
|
||||
try {
|
||||
result[key] = JSON.parse(value) as T;
|
||||
} catch {
|
||||
result[key] = value as unknown as T;
|
||||
for (const key of keys) {
|
||||
const value = constantsMap.get(key);
|
||||
result[key] = (value !== undefined ? value : null) as T | null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const getAllConstValues = async (): Promise<Record<ConstKey, any>> => {
|
||||
const result: Record<string, any> = {};
|
||||
// const result: Record<string, any> = {};
|
||||
//
|
||||
// for (const key of CONST_KEYS_ARRAY) {
|
||||
// result[key] = await getConstant(key);
|
||||
// }
|
||||
//
|
||||
// return result as Record<ConstKey, any>;
|
||||
|
||||
const constants = await getAllKeyValStore();
|
||||
const constantsMap = new Map(constants.map(c => [c.key, c.value]));
|
||||
|
||||
const result: Record<string, any> = {};
|
||||
for (const key of CONST_KEYS_ARRAY) {
|
||||
result[key] = await getConstant(key);
|
||||
result[key] = constantsMap.get(key) ?? null;
|
||||
}
|
||||
|
||||
return result as Record<ConstKey, any>;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import redisClient from '@/src/lib/redis-client'
|
||||
// import redisClient from '@/src/lib/redis-client'
|
||||
|
||||
export async function enqueue(queueName: string, eventData: any): Promise<boolean> {
|
||||
try {
|
||||
const jsonData = JSON.stringify(eventData);
|
||||
const result = await redisClient.lPush(queueName, jsonData);
|
||||
return result > 0;
|
||||
// const result = await redisClient.lPush(queueName, jsonData);
|
||||
// return result > 0;
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Event enqueue error:', error);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
import { createClient, RedisClientType } from 'redis';
|
||||
// import { createClient, RedisClientType } from 'redis';
|
||||
import { redisUrl } from '@/src/lib/env-exporter'
|
||||
|
||||
const createClient = (args:any) => {}
|
||||
class RedisClient {
|
||||
private client: RedisClientType;
|
||||
private subscriberClient: RedisClientType | null = null;
|
||||
private isConnected: boolean = false;
|
||||
// private client: RedisClientType;
|
||||
// private subscriberClient: RedisClientType | null = null;
|
||||
// private isConnected: boolean = false;
|
||||
//
|
||||
private client: any;
|
||||
private subscriberrlient: any;
|
||||
private isConnected: any = false;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.client = createClient({
|
||||
url: redisUrl,
|
||||
});
|
||||
|
||||
this.client.on('error', (err) => {
|
||||
console.error('Redis Client Error:', err);
|
||||
});
|
||||
|
||||
// this.client.on('error', (err) => {
|
||||
// console.error('Redis Client Error:', err);
|
||||
// });
|
||||
//
|
||||
this.client.on('connect', () => {
|
||||
console.log('Redis Client Connected');
|
||||
this.isConnected = true;
|
||||
|
|
@ -34,9 +40,9 @@ class RedisClient {
|
|||
});
|
||||
|
||||
// Connect immediately (fire and forget)
|
||||
this.client.connect().catch((err) => {
|
||||
console.error('Failed to connect Redis:', err);
|
||||
});
|
||||
// this.client.connect().catch((err) => {
|
||||
// console.error('Failed to connect Redis:', err);
|
||||
// });
|
||||
}
|
||||
|
||||
async set(key: string, value: string, ttlSeconds?: number): Promise<string | null> {
|
||||
|
|
@ -79,41 +85,41 @@ class RedisClient {
|
|||
|
||||
// Subscribe to a channel with callback
|
||||
async subscribe(channel: string, callback: (message: string) => void): Promise<void> {
|
||||
if (!this.subscriberClient) {
|
||||
this.subscriberClient = createClient({
|
||||
url: redisUrl,
|
||||
});
|
||||
|
||||
this.subscriberClient.on('error', (err) => {
|
||||
console.error('Redis Subscriber Error:', err);
|
||||
});
|
||||
|
||||
this.subscriberClient.on('connect', () => {
|
||||
console.log('Redis Subscriber Connected');
|
||||
});
|
||||
|
||||
await this.subscriberClient.connect();
|
||||
}
|
||||
|
||||
await this.subscriberClient.subscribe(channel, callback);
|
||||
// if (!this.subscriberClient) {
|
||||
// this.subscriberClient = createClient({
|
||||
// url: redisUrl,
|
||||
// });
|
||||
//
|
||||
// this.subscriberClient.on('error', (err) => {
|
||||
// console.error('Redis Subscriber Error:', err);
|
||||
// });
|
||||
//
|
||||
// this.subscriberClient.on('connect', () => {
|
||||
// console.log('Redis Subscriber Connected');
|
||||
// });
|
||||
//
|
||||
// await this.subscriberClient.connect();
|
||||
// }
|
||||
//
|
||||
// await this.subscriberClient.subscribe(channel, callback);
|
||||
console.log(`Subscribed to channel: ${channel}`);
|
||||
}
|
||||
|
||||
// Unsubscribe from a channel
|
||||
async unsubscribe(channel: string): Promise<void> {
|
||||
if (this.subscriberClient) {
|
||||
await this.subscriberClient.unsubscribe(channel);
|
||||
console.log(`Unsubscribed from channel: ${channel}`);
|
||||
}
|
||||
// if (this.subscriberClient) {
|
||||
// await this.subscriberClient.unsubscribe(channel);
|
||||
// console.log(`Unsubscribed from channel: ${channel}`);
|
||||
// }
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
if (this.isConnected) {
|
||||
this.client.disconnect();
|
||||
}
|
||||
if (this.subscriberClient) {
|
||||
this.subscriberClient.disconnect();
|
||||
}
|
||||
// if (this.isConnected) {
|
||||
// this.client.disconnect();
|
||||
// }
|
||||
// if (this.subscriberClient) {
|
||||
// this.subscriberClient.disconnect();
|
||||
// }
|
||||
}
|
||||
|
||||
get isClientConnected(): boolean {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { jwtVerify } from 'jose';
|
||||
import { getStaffUserById, isUserSuspended } from '@/src/dbService';
|
||||
import { ApiError } from '@/src/lib/api-error';
|
||||
import { encodedJwtSecret } from '@/src/lib/env-exporter';
|
||||
|
||||
interface AuthenticatedRequest extends Request {
|
||||
user?: {
|
||||
|
|
@ -27,7 +28,8 @@ export const authenticateUser = async (req: AuthenticatedRequest, res: Response,
|
|||
const token = authHeader.substring(7);
|
||||
console.log(req.headers)
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key') as any;
|
||||
const { payload } = await jwtVerify(token, encodedJwtSecret);
|
||||
const decoded = payload as any;
|
||||
|
||||
// Check if this is a staff token (has staffId)
|
||||
if (decoded.staffId) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { jwtVerify, errors } from 'jose';
|
||||
import { ApiError } from '@/src/lib/api-error'
|
||||
import { encodedJwtSecret } from '@/src/lib/env-exporter';
|
||||
|
||||
// Extend the Request interface to include user property
|
||||
declare global {
|
||||
|
|
@ -11,7 +12,7 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
export const verifyToken = (req: Request, res: Response, next: NextFunction) => {
|
||||
export const verifyToken = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Get token from Authorization header
|
||||
const authHeader = req.headers.authorization;
|
||||
|
|
@ -28,15 +29,15 @@ export const verifyToken = (req: Request, res: Response, next: NextFunction) =>
|
|||
}
|
||||
|
||||
// Verify token
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key');
|
||||
const { payload } = await jwtVerify(token, encodedJwtSecret);
|
||||
|
||||
|
||||
// Add user info to request
|
||||
req.user = decoded;
|
||||
req.user = payload;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.JsonWebTokenError) {
|
||||
if (error instanceof errors.JOSEError) {
|
||||
next(new ApiError('Invalid Auth Credentials', 401));
|
||||
} else {
|
||||
next(error);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { jwtVerify, errors } from 'jose';
|
||||
import { getStaffUserById } from '@/src/dbService';
|
||||
import { ApiError } from '@/src/lib/api-error';
|
||||
import { encodedJwtSecret } from '@/src/lib/env-exporter';
|
||||
|
||||
// Extend Request interface to include staffUser
|
||||
declare global {
|
||||
|
|
@ -18,9 +19,10 @@ declare global {
|
|||
/**
|
||||
* Verify JWT token and extract payload
|
||||
*/
|
||||
const verifyStaffToken = (token: string) => {
|
||||
const verifyStaffToken = async (token: string) => {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_SECRET || 'default-secret');
|
||||
const { payload } = await jwtVerify(token, encodedJwtSecret);
|
||||
return payload;
|
||||
} catch (error) {
|
||||
throw new ApiError('Access denied. Invalid auth credentials', 401);
|
||||
}
|
||||
|
|
@ -45,7 +47,7 @@ export const authenticateStaff = async (req: Request, res: Response, next: NextF
|
|||
}
|
||||
|
||||
// Verify token and extract payload
|
||||
const decoded = verifyStaffToken(token) as any;
|
||||
const decoded = await verifyStaffToken(token) as any;
|
||||
|
||||
// Verify staffId exists in token
|
||||
if (!decoded.staffId) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import redisClient from '@/src/lib/redis-client'
|
||||
// import redisClient from '@/src/lib/redis-client'
|
||||
import {
|
||||
getAllBannersForCache,
|
||||
getBanners,
|
||||
getBannerById as getBannerByIdFromDb,
|
||||
type BannerData,
|
||||
} from '@/src/dbService'
|
||||
import { scaffoldAssetUrl } from '@/src/lib/s3-client'
|
||||
|
|
@ -34,22 +36,22 @@ export async function initializeBannerStore(): Promise<void> {
|
|||
*/
|
||||
|
||||
// Store each banner in Redis
|
||||
for (const banner of banners) {
|
||||
const signedImageUrl = banner.imageUrl
|
||||
? scaffoldAssetUrl(banner.imageUrl)
|
||||
: banner.imageUrl
|
||||
|
||||
const bannerObj: Banner = {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: signedImageUrl,
|
||||
serialNum: banner.serialNum,
|
||||
productIds: banner.productIds,
|
||||
createdAt: banner.createdAt,
|
||||
}
|
||||
|
||||
await redisClient.set(`banner:${banner.id}`, JSON.stringify(bannerObj))
|
||||
}
|
||||
// for (const banner of banners) {
|
||||
// const signedImageUrl = banner.imageUrl
|
||||
// ? scaffoldAssetUrl(banner.imageUrl)
|
||||
// : banner.imageUrl
|
||||
//
|
||||
// const bannerObj: Banner = {
|
||||
// id: banner.id,
|
||||
// name: banner.name,
|
||||
// imageUrl: signedImageUrl,
|
||||
// serialNum: banner.serialNum,
|
||||
// productIds: banner.productIds,
|
||||
// createdAt: banner.createdAt,
|
||||
// }
|
||||
//
|
||||
// await redisClient.set(`banner:${banner.id}`, JSON.stringify(bannerObj))
|
||||
// }
|
||||
|
||||
console.log('Banner store initialized successfully')
|
||||
} catch (error) {
|
||||
|
|
@ -59,10 +61,26 @@ export async function initializeBannerStore(): Promise<void> {
|
|||
|
||||
export async function getBannerById(id: number): Promise<Banner | null> {
|
||||
try {
|
||||
const key = `banner:${id}`
|
||||
const data = await redisClient.get(key)
|
||||
if (!data) return null
|
||||
return JSON.parse(data) as Banner
|
||||
// const key = `banner:${id}`
|
||||
// const data = await redisClient.get(key)
|
||||
// if (!data) return null
|
||||
// return JSON.parse(data) as Banner
|
||||
|
||||
const banner = await getBannerByIdFromDb(id)
|
||||
if (!banner) return null
|
||||
|
||||
const signedImageUrl = banner.imageUrl
|
||||
? scaffoldAssetUrl(banner.imageUrl)
|
||||
: banner.imageUrl
|
||||
|
||||
return {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: signedImageUrl,
|
||||
serialNum: banner.serialNum,
|
||||
productIds: banner.productIds,
|
||||
createdAt: banner.createdAt,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error getting banner ${id}:`, error)
|
||||
return null
|
||||
|
|
@ -72,24 +90,41 @@ export async function getBannerById(id: number): Promise<Banner | null> {
|
|||
export async function getAllBanners(): Promise<Banner[]> {
|
||||
try {
|
||||
// Get all keys matching the pattern "banner:*"
|
||||
const keys = await redisClient.KEYS('banner:*')
|
||||
// const keys = await redisClient.KEYS('banner:*')
|
||||
//
|
||||
// if (keys.length === 0) return []
|
||||
//
|
||||
// // Get all banners using MGET for better performance
|
||||
// const bannersData = await redisClient.MGET(keys)
|
||||
//
|
||||
// const banners: Banner[] = []
|
||||
// for (const bannerData of bannersData) {
|
||||
// if (bannerData) {
|
||||
// banners.push(JSON.parse(bannerData) as Banner)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Sort by serialNum to maintain the same order as the original query
|
||||
// banners.sort((a, b) => (a.serialNum || 0) - (b.serialNum || 0))
|
||||
//
|
||||
// return banners
|
||||
|
||||
if (keys.length === 0) return []
|
||||
const banners = await getBanners()
|
||||
|
||||
// Get all banners using MGET for better performance
|
||||
const bannersData = await redisClient.MGET(keys)
|
||||
return banners.map((banner) => {
|
||||
const signedImageUrl = banner.imageUrl
|
||||
? scaffoldAssetUrl(banner.imageUrl)
|
||||
: banner.imageUrl
|
||||
|
||||
const banners: Banner[] = []
|
||||
for (const bannerData of bannersData) {
|
||||
if (bannerData) {
|
||||
banners.push(JSON.parse(bannerData) as Banner)
|
||||
return {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: signedImageUrl,
|
||||
serialNum: banner.serialNum,
|
||||
productIds: banner.productIds,
|
||||
createdAt: banner.createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by serialNum to maintain the same order as the original query
|
||||
banners.sort((a, b) => (a.serialNum || 0) - (b.serialNum || 0))
|
||||
|
||||
return banners
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error getting all banners:', error)
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import redisClient from '@/src/lib/redis-client'
|
||||
// import redisClient from '@/src/lib/redis-client'
|
||||
import {
|
||||
getAllProductsForCache,
|
||||
getAllStoresForCache,
|
||||
getAllDeliverySlotsForCache,
|
||||
getAllSpecialDealsForCache,
|
||||
getAllProductTagsForCache,
|
||||
getProductById as getProductByIdFromDb,
|
||||
type ProductBasicData,
|
||||
type StoreBasicData,
|
||||
type DeliverySlotData,
|
||||
|
|
@ -99,6 +100,178 @@ export async function initializeProducts(): Promise<void> {
|
|||
}
|
||||
|
||||
// Store each product in Redis
|
||||
// for (const product of productsData) {
|
||||
// const signedImages = scaffoldAssetUrl(
|
||||
// (product.images as string[]) || []
|
||||
// )
|
||||
// const store = product.storeId
|
||||
// ? storeMap.get(product.storeId) || null
|
||||
// : null
|
||||
// const deliverySlots = deliverySlotsMap.get(product.id) || []
|
||||
// const specialDeals = specialDealsMap.get(product.id) || []
|
||||
// const productTags = productTagsMap.get(product.id) || []
|
||||
//
|
||||
// const productObj: Product = {
|
||||
// id: product.id,
|
||||
// name: product.name,
|
||||
// shortDescription: product.shortDescription,
|
||||
// longDescription: product.longDescription,
|
||||
// price: product.price.toString(),
|
||||
// marketPrice: product.marketPrice?.toString() || null,
|
||||
// unitNotation: product.unitShortNotation,
|
||||
// images: signedImages,
|
||||
// isOutOfStock: product.isOutOfStock,
|
||||
// store: store
|
||||
// ? { id: store.id, name: store.name, description: store.description }
|
||||
// : null,
|
||||
// incrementStep: product.incrementStep,
|
||||
// productQuantity: product.productQuantity,
|
||||
// isFlashAvailable: product.isFlashAvailable,
|
||||
// flashPrice: product.flashPrice?.toString() || null,
|
||||
// deliverySlots: deliverySlots.map((s) => ({
|
||||
// id: s.id,
|
||||
// deliveryTime: s.deliveryTime,
|
||||
// freezeTime: s.freezeTime,
|
||||
// isCapacityFull: s.isCapacityFull,
|
||||
// })),
|
||||
// specialDeals: specialDeals.map((d) => ({
|
||||
// quantity: d.quantity.toString(),
|
||||
// price: d.price.toString(),
|
||||
// validTill: d.validTill,
|
||||
// })),
|
||||
// productTags: productTags,
|
||||
// }
|
||||
//
|
||||
// await redisClient.set(`product:${product.id}`, JSON.stringify(productObj))
|
||||
// }
|
||||
|
||||
console.log('Product store initialized successfully')
|
||||
} catch (error) {
|
||||
console.error('Error initializing product store:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getProductById(id: number): Promise<Product | null> {
|
||||
try {
|
||||
// const key = `product:${id}`
|
||||
// const data = await redisClient.get(key)
|
||||
// if (!data) return null
|
||||
// return JSON.parse(data) as Product
|
||||
|
||||
const product = await getProductByIdFromDb(id)
|
||||
if (!product) return null
|
||||
|
||||
const signedImages = scaffoldAssetUrl(
|
||||
(product.images as string[]) || []
|
||||
)
|
||||
|
||||
// Fetch store info
|
||||
const allStores = await getAllStoresForCache()
|
||||
const store = product.storeId
|
||||
? allStores.find(s => s.id === product.storeId) || null
|
||||
: null
|
||||
|
||||
// Fetch delivery slots for this product
|
||||
const allDeliverySlots = await getAllDeliverySlotsForCache()
|
||||
const productSlots = allDeliverySlots.filter(s => s.productId === id)
|
||||
|
||||
// Fetch special deals for this product
|
||||
const allSpecialDeals = await getAllSpecialDealsForCache()
|
||||
const productDeals = allSpecialDeals.filter(d => d.productId === id)
|
||||
|
||||
// Fetch product tags for this product
|
||||
const allProductTags = await getAllProductTagsForCache()
|
||||
const productTagNames = allProductTags
|
||||
.filter(t => t.productId === id)
|
||||
.map(t => t.tagName)
|
||||
|
||||
return {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
shortDescription: product.shortDescription,
|
||||
longDescription: product.longDescription,
|
||||
price: product.price.toString(),
|
||||
marketPrice: product.marketPrice?.toString() || null,
|
||||
unitNotation: product.unit.shortNotation,
|
||||
images: signedImages,
|
||||
isOutOfStock: product.isOutOfStock,
|
||||
store: store
|
||||
? { id: store.id, name: store.name, description: store.description }
|
||||
: null,
|
||||
incrementStep: product.incrementStep,
|
||||
productQuantity: product.productQuantity,
|
||||
isFlashAvailable: product.isFlashAvailable,
|
||||
flashPrice: product.flashPrice?.toString() || null,
|
||||
deliverySlots: productSlots.map((s) => ({
|
||||
id: s.id,
|
||||
deliveryTime: s.deliveryTime,
|
||||
freezeTime: s.freezeTime,
|
||||
isCapacityFull: s.isCapacityFull,
|
||||
})),
|
||||
specialDeals: productDeals.map((d) => ({
|
||||
quantity: d.quantity.toString(),
|
||||
price: d.price.toString(),
|
||||
validTill: d.validTill,
|
||||
})),
|
||||
productTags: productTagNames,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error getting product ${id}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllProducts(): Promise<Product[]> {
|
||||
try {
|
||||
// Get all keys matching the pattern "product:*"
|
||||
// const keys = await redisClient.KEYS('product:*')
|
||||
//
|
||||
// if (keys.length === 0) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// // Get all products using MGET for better performance
|
||||
// const productsData = await redisClient.MGET(keys)
|
||||
//
|
||||
// const products: Product[] = []
|
||||
// for (const productData of productsData) {
|
||||
// if (productData) {
|
||||
// products.push(JSON.parse(productData) as Product)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return products
|
||||
|
||||
const productsData = await getAllProductsForCache()
|
||||
|
||||
const allStores = await getAllStoresForCache()
|
||||
const storeMap = new Map(allStores.map((s) => [s.id, s]))
|
||||
|
||||
const allDeliverySlots = await getAllDeliverySlotsForCache()
|
||||
const deliverySlotsMap = new Map<number, DeliverySlotData[]>()
|
||||
for (const slot of allDeliverySlots) {
|
||||
if (!deliverySlotsMap.has(slot.productId))
|
||||
deliverySlotsMap.set(slot.productId, [])
|
||||
deliverySlotsMap.get(slot.productId)!.push(slot)
|
||||
}
|
||||
|
||||
const allSpecialDeals = await getAllSpecialDealsForCache()
|
||||
const specialDealsMap = new Map<number, SpecialDealData[]>()
|
||||
for (const deal of allSpecialDeals) {
|
||||
if (!specialDealsMap.has(deal.productId))
|
||||
specialDealsMap.set(deal.productId, [])
|
||||
specialDealsMap.get(deal.productId)!.push(deal)
|
||||
}
|
||||
|
||||
const allProductTags = await getAllProductTagsForCache()
|
||||
const productTagsMap = new Map<number, string[]>()
|
||||
for (const tag of allProductTags) {
|
||||
if (!productTagsMap.has(tag.productId))
|
||||
productTagsMap.set(tag.productId, [])
|
||||
productTagsMap.get(tag.productId)!.push(tag.tagName)
|
||||
}
|
||||
|
||||
const products: Product[] = []
|
||||
for (const product of productsData) {
|
||||
const signedImages = scaffoldAssetUrl(
|
||||
(product.images as string[]) || []
|
||||
|
|
@ -110,7 +283,7 @@ export async function initializeProducts(): Promise<void> {
|
|||
const specialDeals = specialDealsMap.get(product.id) || []
|
||||
const productTags = productTagsMap.get(product.id) || []
|
||||
|
||||
const productObj: Product = {
|
||||
products.push({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
shortDescription: product.shortDescription,
|
||||
|
|
@ -139,46 +312,7 @@ export async function initializeProducts(): Promise<void> {
|
|||
validTill: d.validTill,
|
||||
})),
|
||||
productTags: productTags,
|
||||
}
|
||||
|
||||
await redisClient.set(`product:${product.id}`, JSON.stringify(productObj))
|
||||
}
|
||||
|
||||
console.log('Product store initialized successfully')
|
||||
} catch (error) {
|
||||
console.error('Error initializing product store:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getProductById(id: number): Promise<Product | null> {
|
||||
try {
|
||||
const key = `product:${id}`
|
||||
const data = await redisClient.get(key)
|
||||
if (!data) return null
|
||||
return JSON.parse(data) as Product
|
||||
} catch (error) {
|
||||
console.error(`Error getting product ${id}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllProducts(): Promise<Product[]> {
|
||||
try {
|
||||
// Get all keys matching the pattern "product:*"
|
||||
const keys = await redisClient.KEYS('product:*')
|
||||
|
||||
if (keys.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Get all products using MGET for better performance
|
||||
const productsData = await redisClient.MGET(keys)
|
||||
|
||||
const products: Product[] = []
|
||||
for (const productData of productsData) {
|
||||
if (productData) {
|
||||
products.push(JSON.parse(productData) as Product)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return products
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import redisClient from '@/src/lib/redis-client'
|
||||
// import redisClient from '@/src/lib/redis-client'
|
||||
import {
|
||||
getAllTagsForCache,
|
||||
getAllTagProductMappings,
|
||||
getAllProductTags,
|
||||
getProductTagById as getProductTagByIdFromDb,
|
||||
type TagBasicData,
|
||||
type TagProductMapping,
|
||||
} from '@/src/dbService'
|
||||
|
|
@ -18,6 +20,30 @@ interface Tag {
|
|||
productIds: number[]
|
||||
}
|
||||
|
||||
async function transformTagToStoreTag(tag: {
|
||||
id: number
|
||||
tagName: string
|
||||
tagDescription: string | null
|
||||
imageUrl: string | null
|
||||
isDashboardTag: boolean
|
||||
relatedStores: unknown
|
||||
products?: Array<{ productId: number }>
|
||||
}): Promise<Tag> {
|
||||
const signedImageUrl = tag.imageUrl
|
||||
? await generateSignedUrlFromS3Url(tag.imageUrl)
|
||||
: null
|
||||
|
||||
return {
|
||||
id: tag.id,
|
||||
tagName: tag.tagName,
|
||||
tagDescription: tag.tagDescription,
|
||||
imageUrl: signedImageUrl,
|
||||
isDashboardTag: tag.isDashboardTag,
|
||||
relatedStores: (tag.relatedStores as number[]) || [],
|
||||
productIds: tag.products ? tag.products.map(p => p.productId) : [],
|
||||
}
|
||||
}
|
||||
|
||||
export async function initializeProductTagStore(): Promise<void> {
|
||||
try {
|
||||
console.log('Initializing product tag store in Redis...')
|
||||
|
|
@ -70,23 +96,23 @@ export async function initializeProductTagStore(): Promise<void> {
|
|||
}
|
||||
|
||||
// Store each tag in Redis
|
||||
for (const tag of tagsData) {
|
||||
const signedImageUrl = tag.imageUrl
|
||||
? await generateSignedUrlFromS3Url(tag.imageUrl)
|
||||
: null
|
||||
|
||||
const tagObj: Tag = {
|
||||
id: tag.id,
|
||||
tagName: tag.tagName,
|
||||
tagDescription: tag.tagDescription,
|
||||
imageUrl: signedImageUrl,
|
||||
isDashboardTag: tag.isDashboardTag,
|
||||
relatedStores: (tag.relatedStores as number[]) || [],
|
||||
productIds: productIdsByTag.get(tag.id) || [],
|
||||
}
|
||||
|
||||
await redisClient.set(`tag:${tag.id}`, JSON.stringify(tagObj))
|
||||
}
|
||||
// for (const tag of tagsData) {
|
||||
// const signedImageUrl = tag.imageUrl
|
||||
// ? await generateSignedUrlFromS3Url(tag.imageUrl)
|
||||
// : null
|
||||
//
|
||||
// const tagObj: Tag = {
|
||||
// id: tag.id,
|
||||
// tagName: tag.tagName,
|
||||
// tagDescription: tag.tagDescription,
|
||||
// imageUrl: signedImageUrl,
|
||||
// isDashboardTag: tag.isDashboardTag,
|
||||
// relatedStores: (tag.relatedStores as number[]) || [],
|
||||
// productIds: productIdsByTag.get(tag.id) || [],
|
||||
// }
|
||||
//
|
||||
// await redisClient.set(`tag:${tag.id}`, JSON.stringify(tagObj))
|
||||
// }
|
||||
|
||||
console.log('Product tag store initialized successfully')
|
||||
} catch (error) {
|
||||
|
|
@ -96,10 +122,15 @@ export async function initializeProductTagStore(): Promise<void> {
|
|||
|
||||
export async function getTagById(id: number): Promise<Tag | null> {
|
||||
try {
|
||||
const key = `tag:${id}`
|
||||
const data = await redisClient.get(key)
|
||||
if (!data) return null
|
||||
return JSON.parse(data) as Tag
|
||||
// const key = `tag:${id}`
|
||||
// const data = await redisClient.get(key)
|
||||
// if (!data) return null
|
||||
// return JSON.parse(data) as Tag
|
||||
|
||||
const tag = await getProductTagByIdFromDb(id)
|
||||
if (!tag) return null
|
||||
|
||||
return transformTagToStoreTag(tag)
|
||||
} catch (error) {
|
||||
console.error(`Error getting tag ${id}:`, error)
|
||||
return null
|
||||
|
|
@ -109,23 +140,31 @@ export async function getTagById(id: number): Promise<Tag | null> {
|
|||
export async function getAllTags(): Promise<Tag[]> {
|
||||
try {
|
||||
// Get all keys matching the pattern "tag:*"
|
||||
const keys = await redisClient.KEYS('tag:*')
|
||||
// const keys = await redisClient.KEYS('tag:*')
|
||||
//
|
||||
// if (keys.length === 0) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// // Get all tags using MGET for better performance
|
||||
// const tagsData = await redisClient.MGET(keys)
|
||||
//
|
||||
// const tags: Tag[] = []
|
||||
// for (const tagData of tagsData) {
|
||||
// if (tagData) {
|
||||
// tags.push(JSON.parse(tagData) as Tag)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return tags
|
||||
|
||||
if (keys.length === 0) {
|
||||
return []
|
||||
const tags = await getAllProductTags()
|
||||
|
||||
const result: Tag[] = []
|
||||
for (const tag of tags) {
|
||||
result.push(await transformTagToStoreTag(tag))
|
||||
}
|
||||
|
||||
// Get all tags using MGET for better performance
|
||||
const tagsData = await redisClient.MGET(keys)
|
||||
|
||||
const tags: Tag[] = []
|
||||
for (const tagData of tagsData) {
|
||||
if (tagData) {
|
||||
tags.push(JSON.parse(tagData) as Tag)
|
||||
}
|
||||
}
|
||||
|
||||
return tags
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error getting all tags:', error)
|
||||
return []
|
||||
|
|
@ -135,26 +174,36 @@ export async function getAllTags(): Promise<Tag[]> {
|
|||
export async function getDashboardTags(): Promise<Tag[]> {
|
||||
try {
|
||||
// Get all keys matching the pattern "tag:*"
|
||||
const keys = await redisClient.KEYS('tag:*')
|
||||
// const keys = await redisClient.KEYS('tag:*')
|
||||
//
|
||||
// if (keys.length === 0) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// // Get all tags using MGET for better performance
|
||||
// const tagsData = await redisClient.MGET(keys)
|
||||
//
|
||||
// const dashboardTags: Tag[] = []
|
||||
// for (const tagData of tagsData) {
|
||||
// if (tagData) {
|
||||
// const tag = JSON.parse(tagData) as Tag
|
||||
// if (tag.isDashboardTag) {
|
||||
// dashboardTags.push(tag)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return dashboardTags
|
||||
|
||||
if (keys.length === 0) {
|
||||
return []
|
||||
}
|
||||
const tags = await getAllProductTags()
|
||||
|
||||
// Get all tags using MGET for better performance
|
||||
const tagsData = await redisClient.MGET(keys)
|
||||
|
||||
const dashboardTags: Tag[] = []
|
||||
for (const tagData of tagsData) {
|
||||
if (tagData) {
|
||||
const tag = JSON.parse(tagData) as Tag
|
||||
const result: Tag[] = []
|
||||
for (const tag of tags) {
|
||||
if (tag.isDashboardTag) {
|
||||
dashboardTags.push(tag)
|
||||
result.push(await transformTagToStoreTag(tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dashboardTags
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error getting dashboard tags:', error)
|
||||
return []
|
||||
|
|
@ -164,26 +213,37 @@ export async function getDashboardTags(): Promise<Tag[]> {
|
|||
export async function getTagsByStoreId(storeId: number): Promise<Tag[]> {
|
||||
try {
|
||||
// Get all keys matching the pattern "tag:*"
|
||||
const keys = await redisClient.KEYS('tag:*')
|
||||
// const keys = await redisClient.KEYS('tag:*')
|
||||
//
|
||||
// if (keys.length === 0) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// // Get all tags using MGET for better performance
|
||||
// const tagsData = await redisClient.MGET(keys)
|
||||
//
|
||||
// const storeTags: Tag[] = []
|
||||
// for (const tagData of tagsData) {
|
||||
// if (tagData) {
|
||||
// const tag = JSON.parse(tagData) as Tag
|
||||
// if (tag.relatedStores.includes(storeId)) {
|
||||
// storeTags.push(tag)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return storeTags
|
||||
|
||||
if (keys.length === 0) {
|
||||
return []
|
||||
}
|
||||
const tags = await getAllProductTags()
|
||||
|
||||
// Get all tags using MGET for better performance
|
||||
const tagsData = await redisClient.MGET(keys)
|
||||
|
||||
const storeTags: Tag[] = []
|
||||
for (const tagData of tagsData) {
|
||||
if (tagData) {
|
||||
const tag = JSON.parse(tagData) as Tag
|
||||
if (tag.relatedStores.includes(storeId)) {
|
||||
storeTags.push(tag)
|
||||
const result: Tag[] = []
|
||||
for (const tag of tags) {
|
||||
const relatedStores = (tag.relatedStores as number[]) || []
|
||||
if (relatedStores.includes(storeId)) {
|
||||
result.push(await transformTagToStoreTag(tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return storeTags
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error(`Error getting tags for store ${storeId}:`, error)
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import redisClient from '@/src/lib/redis-client'
|
||||
// import redisClient from '@/src/lib/redis-client'
|
||||
import {
|
||||
getAllSlotsWithProductsForCache,
|
||||
type SlotWithProductsData,
|
||||
|
|
@ -35,6 +35,45 @@ interface SlotInfo {
|
|||
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.productSlots.map((productSlot) => ({
|
||||
id: productSlot.product.id,
|
||||
name: productSlot.product.name,
|
||||
productQuantity: productSlot.product.productQuantity,
|
||||
shortDescription: productSlot.product.shortDescription,
|
||||
price: productSlot.product.price.toString(),
|
||||
marketPrice: productSlot.product.marketPrice?.toString() || null,
|
||||
unit: productSlot.product.unit?.shortNotation || null,
|
||||
images: scaffoldAssetUrl(
|
||||
(productSlot.product.images as string[]) || []
|
||||
),
|
||||
isOutOfStock: productSlot.product.isOutOfStock,
|
||||
storeId: productSlot.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...')
|
||||
|
|
@ -99,9 +138,9 @@ export async function initializeSlotStore(): Promise<void> {
|
|||
)
|
||||
|
||||
// Store each slot in Redis with key pattern "slot:{id}"
|
||||
for (const slot of slotsWithProducts) {
|
||||
await redisClient.set(`slot:${slot.id}`, JSON.stringify(slot))
|
||||
}
|
||||
// for (const slot of slotsWithProducts) {
|
||||
// await redisClient.set(`slot:${slot.id}`, JSON.stringify(slot))
|
||||
// }
|
||||
|
||||
// Build and store product-slots map
|
||||
// Group slots by productId
|
||||
|
|
@ -122,12 +161,12 @@ export async function initializeSlotStore(): Promise<void> {
|
|||
}
|
||||
|
||||
// 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)
|
||||
)
|
||||
}
|
||||
// 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) {
|
||||
|
|
@ -137,10 +176,16 @@ export async function initializeSlotStore(): Promise<void> {
|
|||
|
||||
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 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
|
||||
|
|
@ -150,21 +195,23 @@ export async function getSlotById(slotId: number): Promise<SlotWithProducts | nu
|
|||
export async function getAllSlots(): Promise<SlotWithProducts[]> {
|
||||
try {
|
||||
// Get all keys matching the pattern "slot:*"
|
||||
const keys = await redisClient.KEYS('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
|
||||
|
||||
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 []
|
||||
|
|
@ -173,10 +220,22 @@ export async function getAllSlots(): Promise<SlotWithProducts[]> {
|
|||
|
||||
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 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.productSlots.some(ps => ps.product.id === productId)
|
||||
if (hasProduct) {
|
||||
productSlots.push(extractSlotInfo(slot))
|
||||
}
|
||||
}
|
||||
|
||||
return productSlots
|
||||
} catch (error) {
|
||||
console.error(`Error getting slots for product ${productId}:`, error)
|
||||
return []
|
||||
|
|
@ -186,23 +245,39 @@ export async function getProductSlots(productId: number): Promise<SlotInfo[]> {
|
|||
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 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 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[]
|
||||
|
||||
for (const slot of slots) {
|
||||
const slotInfo = extractSlotInfo(slot)
|
||||
for (const productSlot of slot.productSlots) {
|
||||
const productId = productSlot.product.id
|
||||
if (!result[productId]) {
|
||||
result[productId] = []
|
||||
}
|
||||
result[productId].push(slotInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,18 +295,38 @@ export async function getMultipleProductsSlots(
|
|||
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 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 (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)
|
||||
|
||||
for (const productId of productIds) {
|
||||
result[productId] = []
|
||||
}
|
||||
|
||||
for (const slot of slots) {
|
||||
const slotInfo = extractSlotInfo(slot)
|
||||
for (const productSlot of slot.productSlots) {
|
||||
const pid = productSlot.product.id
|
||||
if (productIdSet.has(pid) && !slot.isCapacityFull) {
|
||||
result[pid].push(slotInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import redisClient from '@/src/lib/redis-client'
|
||||
// import redisClient from '@/src/lib/redis-client'
|
||||
import {
|
||||
getAllUserNegativityScores as getAllUserNegativityScoresFromDb,
|
||||
getUserNegativityScore as getUserNegativityScoreFromDb,
|
||||
|
|
@ -26,12 +26,12 @@ export async function initializeUserNegativityStore(): Promise<void> {
|
|||
.groupBy(userIncidents.userId);
|
||||
*/
|
||||
|
||||
for (const { userId, totalNegativityScore } of results) {
|
||||
await redisClient.set(
|
||||
`user:negativity:${userId}`,
|
||||
totalNegativityScore.toString()
|
||||
)
|
||||
}
|
||||
// for (const { userId, totalNegativityScore } of results) {
|
||||
// await redisClient.set(
|
||||
// `user:negativity:${userId}`,
|
||||
// totalNegativityScore.toString()
|
||||
// )
|
||||
// }
|
||||
|
||||
console.log(`User negativity store initialized for ${results.length} users`)
|
||||
} catch (error) {
|
||||
|
|
@ -42,14 +42,16 @@ export async function initializeUserNegativityStore(): Promise<void> {
|
|||
|
||||
export async function getUserNegativity(userId: number): Promise<number> {
|
||||
try {
|
||||
const key = `user:negativity:${userId}`
|
||||
const data = await redisClient.get(key)
|
||||
// const key = `user:negativity:${userId}`
|
||||
// const data = await redisClient.get(key)
|
||||
//
|
||||
// if (!data) {
|
||||
// return 0
|
||||
// }
|
||||
//
|
||||
// return parseInt(data, 10)
|
||||
|
||||
if (!data) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return parseInt(data, 10)
|
||||
return await getUserNegativityScoreFromDb(userId)
|
||||
} catch (error) {
|
||||
console.error(`Error getting negativity score for user ${userId}:`, error)
|
||||
return 0
|
||||
|
|
@ -58,24 +60,32 @@ export async function getUserNegativity(userId: number): Promise<number> {
|
|||
|
||||
export async function getAllUserNegativityScores(): Promise<Record<number, number>> {
|
||||
try {
|
||||
const keys = await redisClient.KEYS('user:negativity:*')
|
||||
// const keys = await redisClient.KEYS('user:negativity:*')
|
||||
//
|
||||
// if (keys.length === 0) return {}
|
||||
//
|
||||
// const values = await redisClient.MGET(keys)
|
||||
//
|
||||
// const result: Record<number, number> = {}
|
||||
// for (let i = 0; i < keys.length; i++) {
|
||||
// const key = keys[i]
|
||||
// const value = values[i]
|
||||
//
|
||||
// const match = key.match(/user:negativity:(\d+)/)
|
||||
// if (match && value) {
|
||||
// const userId = parseInt(match[1], 10)
|
||||
// result[userId] = parseInt(value, 10)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return result
|
||||
|
||||
if (keys.length === 0) return {}
|
||||
|
||||
const values = await redisClient.MGET(keys)
|
||||
const results = await getAllUserNegativityScoresFromDb()
|
||||
|
||||
const result: Record<number, number> = {}
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
const value = values[i]
|
||||
|
||||
const match = key.match(/user:negativity:(\d+)/)
|
||||
if (match && value) {
|
||||
const userId = parseInt(match[1], 10)
|
||||
result[userId] = parseInt(value, 10)
|
||||
for (const { userId, totalNegativityScore } of results) {
|
||||
result[userId] = totalNegativityScore
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error getting all user negativity scores:', error)
|
||||
|
|
@ -89,20 +99,26 @@ export async function getMultipleUserNegativityScores(
|
|||
try {
|
||||
if (userIds.length === 0) return {}
|
||||
|
||||
const keys = userIds.map((id) => `user:negativity:${id}`)
|
||||
|
||||
const values = await redisClient.MGET(keys)
|
||||
// const keys = userIds.map((id) => `user:negativity:${id}`)
|
||||
//
|
||||
// const values = await redisClient.MGET(keys)
|
||||
//
|
||||
// const result: Record<number, number> = {}
|
||||
// for (let i = 0; i < userIds.length; i++) {
|
||||
// const value = values[i]
|
||||
// if (value) {
|
||||
// result[userIds[i]] = parseInt(value, 10)
|
||||
// } else {
|
||||
// result[userIds[i]] = 0
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return result
|
||||
|
||||
const result: Record<number, number> = {}
|
||||
for (let i = 0; i < userIds.length; i++) {
|
||||
const value = values[i]
|
||||
if (value) {
|
||||
result[userIds[i]] = parseInt(value, 10)
|
||||
} else {
|
||||
result[userIds[i]] = 0
|
||||
for (const userId of userIds) {
|
||||
result[userId] = await getUserNegativityScoreFromDb(userId)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error getting multiple user negativity scores:', error)
|
||||
|
|
@ -131,8 +147,8 @@ export async function recomputeUserNegativityScore(userId: number): Promise<void
|
|||
const totalScore = result?.totalNegativityScore || 0;
|
||||
*/
|
||||
|
||||
const key = `user:negativity:${userId}`
|
||||
await redisClient.set(key, totalScore.toString())
|
||||
// const key = `user:negativity:${userId}`
|
||||
// await redisClient.set(key, totalScore.toString())
|
||||
} catch (error) {
|
||||
console.error(`Error recomputing negativity score for user ${userId}:`, error)
|
||||
throw error
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { TRPCError } from "@trpc/server";
|
|||
import { z } from "zod";
|
||||
import { ApiError } from "@/src/lib/api-error"
|
||||
import { appUrl } from "@/src/lib/env-exporter"
|
||||
import redisClient from "@/src/lib/redis-client"
|
||||
import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
|
||||
// import redisClient from "@/src/lib/redis-client"
|
||||
// import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
|
||||
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
||||
import {
|
||||
getActiveSlotsWithProducts as getActiveSlotsWithProductsInDb,
|
||||
|
|
@ -595,21 +595,21 @@ export const slotsRouter = router({
|
|||
|
||||
const { id } = input;
|
||||
const slotId = parseInt(id);
|
||||
const cacheKey = getSlotSequenceKey(slotId);
|
||||
// const cacheKey = getSlotSequenceKey(slotId);
|
||||
|
||||
try {
|
||||
const cached = await redisClient.get(cacheKey);
|
||||
if (cached) {
|
||||
const parsed = JSON.parse(cached);
|
||||
const validated = cachedSequenceSchema.parse(parsed);
|
||||
console.log('sending cached response')
|
||||
|
||||
return { deliverySequence: validated };
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Redis cache read/validation failed, falling back to DB:', error);
|
||||
// Continue to DB fallback
|
||||
}
|
||||
// try {
|
||||
// const cached = await redisClient.get(cacheKey);
|
||||
// if (cached) {
|
||||
// const parsed = JSON.parse(cached);
|
||||
// const validated = cachedSequenceSchema.parse(parsed);
|
||||
// console.log('sending cached response')
|
||||
//
|
||||
// return { deliverySequence: validated };
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.warn('Redis cache read/validation failed, falling back to DB:', error);
|
||||
// // Continue to DB fallback
|
||||
// }
|
||||
|
||||
// Fallback to DB
|
||||
const slot = await getSlotDeliverySequenceInDb(slotId)
|
||||
|
|
@ -634,12 +634,12 @@ export const slotsRouter = router({
|
|||
const sequence = (slot.deliverySequence || {}) as CachedDeliverySequence;
|
||||
|
||||
// Cache the validated result
|
||||
try {
|
||||
const validated = cachedSequenceSchema.parse(sequence);
|
||||
await redisClient.set(cacheKey, JSON.stringify(validated), 3600);
|
||||
} catch (cacheError) {
|
||||
console.warn('Redis cache write failed:', cacheError);
|
||||
}
|
||||
// try {
|
||||
// const validated = cachedSequenceSchema.parse(sequence);
|
||||
// await redisClient.set(cacheKey, JSON.stringify(validated), 3600);
|
||||
// } catch (cacheError) {
|
||||
// console.warn('Redis cache write failed:', cacheError);
|
||||
// }
|
||||
|
||||
return { deliverySequence: sequence }
|
||||
}),
|
||||
|
|
@ -676,13 +676,13 @@ export const slotsRouter = router({
|
|||
}
|
||||
|
||||
// Cache the updated sequence
|
||||
const cacheKey = getSlotSequenceKey(id);
|
||||
try {
|
||||
const validated = cachedSequenceSchema.parse(deliverySequence);
|
||||
await redisClient.set(cacheKey, JSON.stringify(validated), 3600);
|
||||
} catch (cacheError) {
|
||||
console.warn('Redis cache write failed:', cacheError);
|
||||
}
|
||||
// const cacheKey = getSlotSequenceKey(id);
|
||||
// try {
|
||||
// const validated = cachedSequenceSchema.parse(deliverySequence);
|
||||
// await redisClient.set(cacheKey, JSON.stringify(validated), 3600);
|
||||
// } catch (cacheError) {
|
||||
// console.warn('Redis cache write failed:', cacheError);
|
||||
// }
|
||||
|
||||
return {
|
||||
slot: updatedSlot,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||
import { z } from 'zod';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { SignJWT } from 'jose';
|
||||
import { encodedJwtSecret } from '@/src/lib/env-exporter';
|
||||
import { ApiError } from '@/src/lib/api-error'
|
||||
import {
|
||||
getStaffUserByName,
|
||||
|
|
@ -40,11 +41,10 @@ export const staffUserRouter = router({
|
|||
throw new ApiError('Invalid credentials', 401);
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ staffId: staff.id, name: staff.name },
|
||||
process.env.JWT_SECRET || 'default-secret',
|
||||
{ expiresIn: '30d' }
|
||||
);
|
||||
const token = await new SignJWT({ staffId: staff.id, name: staff.name })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setExpirationTime('30d')
|
||||
.sign(encodedJwtSecret);
|
||||
|
||||
return {
|
||||
message: 'Login successful',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||
import { z } from 'zod'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { SignJWT } from 'jose';
|
||||
import { generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
|
||||
import { ApiError } from '@/src/lib/api-error'
|
||||
import { jwtSecret } from '@/src/lib/env-exporter'
|
||||
import { encodedJwtSecret } from '@/src/lib/env-exporter'
|
||||
import { sendOtp, verifyOtpUtil, getOtpCreds } from '@/src/lib/otp-utils'
|
||||
import {
|
||||
getUserAuthByEmail as getUserAuthByEmailInDb,
|
||||
|
|
@ -41,13 +41,11 @@ interface RegisterRequest {
|
|||
profileImageUrl?: string | null;
|
||||
}
|
||||
|
||||
const generateToken = (userId: number): string => {
|
||||
const secret = jwtSecret;
|
||||
if (!secret) {
|
||||
throw new ApiError('JWT secret not configured', 500);
|
||||
}
|
||||
|
||||
return jwt.sign({ userId }, secret, { expiresIn: '7d' });
|
||||
const generateToken = async (userId: number): Promise<string> => {
|
||||
return await new SignJWT({ userId })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setExpirationTime('7d')
|
||||
.sign(encodedJwtSecret);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -100,7 +98,7 @@ export const authRouter = router({
|
|||
throw new ApiError('Invalid credentials', 401);
|
||||
}
|
||||
|
||||
const token = generateToken(foundUser.id);
|
||||
const token = await generateToken(foundUser.id);
|
||||
|
||||
const response: UserAuthResponse = {
|
||||
token,
|
||||
|
|
@ -177,7 +175,7 @@ export const authRouter = router({
|
|||
profileImage: profileImageUrl ?? null,
|
||||
})
|
||||
|
||||
const token = generateToken(newUser.id);
|
||||
const token = await generateToken(newUser.id);
|
||||
|
||||
const profileImageSignedUrl = profileImageUrl
|
||||
? await generateSignedUrlFromS3Url(profileImageUrl)
|
||||
|
|
@ -235,7 +233,7 @@ export const authRouter = router({
|
|||
}
|
||||
|
||||
// Generate JWT
|
||||
const token = generateToken(user.id);
|
||||
const token = await generateToken(user.id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { router, protectedProcedure, publicProcedure } from '@/src/trpc/trpc-index'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { SignJWT } from 'jose'
|
||||
import { z } from 'zod'
|
||||
import { ApiError } from '@/src/lib/api-error'
|
||||
import { jwtSecret } from '@/src/lib/env-exporter'
|
||||
import { encodedJwtSecret } from '@/src/lib/env-exporter'
|
||||
import { generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
|
||||
import {
|
||||
getUserProfileById as getUserProfileByIdInDb,
|
||||
|
|
@ -19,13 +19,11 @@ import type {
|
|||
UserSavePushTokenResponse,
|
||||
} from '@packages/shared'
|
||||
|
||||
const generateToken = (userId: number): string => {
|
||||
const secret = jwtSecret;
|
||||
if (!secret) {
|
||||
throw new ApiError('JWT secret not configured', 500);
|
||||
}
|
||||
|
||||
return jwt.sign({ userId }, secret, { expiresIn: '7d' });
|
||||
const generateToken = async (userId: number): Promise<string> => {
|
||||
return await new SignJWT({ userId })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setExpirationTime('7d')
|
||||
.sign(encodedJwtSecret);
|
||||
};
|
||||
|
||||
export const userRouter = router({
|
||||
|
|
|
|||
19174
package-lock.json
generated
Normal file
19174
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue