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