This commit is contained in:
shafi54 2026-03-26 18:59:58 +05:30
parent 68103010c6
commit 639428caba
21 changed files with 19950 additions and 403 deletions

View file

@ -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) {

View file

@ -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"
}
}

View file

@ -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);

View file

@ -1,4 +1,3 @@
import * as cron from 'node-cron';
interface PendingPaymentRecord {
payment: any;

View file

@ -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'

View file

@ -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>;

View file

@ -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;

View file

@ -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 {

View file

@ -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) {

View file

@ -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);

View file

@ -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) {

View file

@ -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 []

View file

@ -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

View file

@ -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
if (tag.isDashboardTag) {
dashboardTags.push(tag)
}
const result: Tag[] = []
for (const tag of tags) {
if (tag.isDashboardTag) {
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 []

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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,

View file

@ -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',

View file

@ -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,

View file

@ -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

File diff suppressed because it is too large Load diff