enh
This commit is contained in:
parent
a16e50c6a5
commit
df114be912
6 changed files with 257 additions and 3 deletions
|
|
@ -1,11 +1,13 @@
|
||||||
import './notif-job';
|
import './notif-job';
|
||||||
import { initializeAllStores } from '../stores/store-initializer';
|
import { initializeAllStores } from '../stores/store-initializer';
|
||||||
|
import { startOrderHandler, publishOrder } from './post-order-handler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize all application services
|
* Initialize all application services
|
||||||
* This function handles initialization of:
|
* This function handles initialization of:
|
||||||
* - Role Manager (fetches and caches all roles)
|
* - Role Manager (fetches and caches all roles)
|
||||||
* - Const Store (syncs constants from DB to Redis)
|
* - Const Store (syncs constants from DB to Redis)
|
||||||
|
* - Post Order Handler (Redis Pub/Sub subscriber)
|
||||||
* - Other services can be added here in the future
|
* - Other services can be added here in the future
|
||||||
*/
|
*/
|
||||||
export const initFunc = async (): Promise<void> => {
|
export const initFunc = async (): Promise<void> => {
|
||||||
|
|
@ -18,6 +20,25 @@ export const initFunc = async (): Promise<void> => {
|
||||||
// Notification queue and worker are initialized via import
|
// Notification queue and worker are initialized via import
|
||||||
console.log('Notification queue and worker initialized');
|
console.log('Notification queue and worker initialized');
|
||||||
|
|
||||||
|
// Start post order handler (Redis Pub/Sub subscriber)
|
||||||
|
await startOrderHandler();
|
||||||
|
|
||||||
|
// Wait a moment for subscription to be ready, then publish demo order
|
||||||
|
// setTimeout(async () => {
|
||||||
|
// console.log('Publishing demo order for testing...');
|
||||||
|
// await publishOrder({
|
||||||
|
// orders: [{
|
||||||
|
// deliveryTime: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // Tomorrow
|
||||||
|
// orderPlaceTime: new Date().toISOString(),
|
||||||
|
// totalAmount: 550,
|
||||||
|
// orderItems: [
|
||||||
|
// { productName: "Chicken Breast", quantity: 2 },
|
||||||
|
// { productName: "Mutton Curry Cut", quantity: 1 },
|
||||||
|
// ],
|
||||||
|
// }],
|
||||||
|
// });
|
||||||
|
// }, 20000);
|
||||||
|
|
||||||
console.log('Application initialization completed successfully');
|
console.log('Application initialization completed successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Application initialization failed:', error);
|
console.error('Application initialization failed:', error);
|
||||||
|
|
|
||||||
146
apps/backend/src/lib/post-order-handler.ts
Normal file
146
apps/backend/src/lib/post-order-handler.ts
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import redisClient from './redis-client';
|
||||||
|
import { sendTelegramMessage } from './telegram-service';
|
||||||
|
|
||||||
|
const ORDER_CHANNEL = 'orders:placed';
|
||||||
|
|
||||||
|
interface SimplifiedOrderItem {
|
||||||
|
productName: string;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SimplifiedOrder {
|
||||||
|
deliveryTime: string;
|
||||||
|
orderPlaceTime: string;
|
||||||
|
totalAmount: number;
|
||||||
|
orderItems: SimplifiedOrderItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormattedOrderData {
|
||||||
|
orders: SimplifiedOrder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format order details for Telegram message
|
||||||
|
* @param orderData The order data to format
|
||||||
|
* @returns Formatted message string
|
||||||
|
*/
|
||||||
|
const formatOrderMessage = (orderData: FormattedOrderData): string => {
|
||||||
|
let message = '🛒 <b>New Order Placed</b>\n\n';
|
||||||
|
|
||||||
|
orderData.orders.forEach((order, index) => {
|
||||||
|
if (orderData.orders.length > 1) {
|
||||||
|
message += `<b>Order ${index + 1}</b>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += '📦 <b>Items:</b>\n';
|
||||||
|
order.orderItems.forEach(item => {
|
||||||
|
message += ` • ${item.productName} x${item.quantity}\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
message += `\n💰 <b>Total:</b> ₹${order.totalAmount}\n`;
|
||||||
|
message += `🚚 <b>Delivery:</b> ${new Date(order.deliveryTime).toLocaleString()}\n`;
|
||||||
|
message += `⏰ <b>Ordered:</b> ${new Date(order.orderPlaceTime).toLocaleString()}\n`;
|
||||||
|
|
||||||
|
if (index < orderData.orders.length - 1) {
|
||||||
|
message += '\n---\n\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the post order handler
|
||||||
|
* Subscribes to the orders:placed channel and sends to Telegram
|
||||||
|
*/
|
||||||
|
export const startOrderHandler = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
console.log('Starting post order handler...');
|
||||||
|
|
||||||
|
await redisClient.subscribe(ORDER_CHANNEL, async (message: string) => {
|
||||||
|
try {
|
||||||
|
const orderDetails = JSON.parse(message);
|
||||||
|
console.log('New order received, sending to Telegram...');
|
||||||
|
|
||||||
|
const telegramMessage = formatOrderMessage(orderDetails);
|
||||||
|
await sendTelegramMessage(telegramMessage);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to process order message:', error);
|
||||||
|
// Still try to send raw message to Telegram if parsing fails
|
||||||
|
await sendTelegramMessage(`⚠️ Error parsing order: ${message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Post order handler started successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to start post order handler:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the post order handler
|
||||||
|
*/
|
||||||
|
export const stopOrderHandler = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await redisClient.unsubscribe(ORDER_CHANNEL);
|
||||||
|
console.log('Post order handler stopped');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping post order handler:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish order details to the queue
|
||||||
|
* @param orderDetails Full order details to publish
|
||||||
|
*/
|
||||||
|
export const publishOrder = async (orderDetails: FormattedOrderData): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const message = JSON.stringify(orderDetails);
|
||||||
|
await redisClient.publish(ORDER_CHANNEL, message);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to publish order:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mould raw order data into simplified format and publish
|
||||||
|
* @param createdOrders Array of created orders from the database
|
||||||
|
* @param ordersBySlot Map of slotId to items with product info
|
||||||
|
* @returns Promise<boolean> indicating success or failure
|
||||||
|
*/
|
||||||
|
export const publishFormattedOrder = async (
|
||||||
|
createdOrders: any[],
|
||||||
|
ordersBySlot: Map<number | null, any[]>
|
||||||
|
): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const simplifiedOrders: SimplifiedOrder[] = createdOrders.map(order => {
|
||||||
|
// Get items for this order from ordersBySlot
|
||||||
|
const slotItems = ordersBySlot.get(order.slotId) || [];
|
||||||
|
|
||||||
|
// Map items to simplified format
|
||||||
|
const orderItems: SimplifiedOrderItem[] = slotItems.map(item => ({
|
||||||
|
productName: item.product?.name || 'Unknown Product',
|
||||||
|
quantity: item.quantity,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
deliveryTime: order.slot?.deliveryTime?.toISOString() || order.deliveryDate?.toISOString() || new Date().toISOString(),
|
||||||
|
orderPlaceTime: order.createdAt?.toISOString() || new Date().toISOString(),
|
||||||
|
totalAmount: parseFloat(order.totalAmount?.toString() || '0'),
|
||||||
|
orderItems,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedData: FormattedOrderData = {
|
||||||
|
orders: simplifiedOrders,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await publishOrder(formattedData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to format and publish order:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -3,6 +3,7 @@ import { redisUrl } from './env-exporter';
|
||||||
|
|
||||||
class RedisClient {
|
class RedisClient {
|
||||||
private client: RedisClientType;
|
private client: RedisClientType;
|
||||||
|
private subscriberClient: RedisClientType | null = null;
|
||||||
private isConnected: boolean = false;
|
private isConnected: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -71,10 +72,48 @@ class RedisClient {
|
||||||
return await this.client.MGET(keys);
|
return await this.client.MGET(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publish message to a channel
|
||||||
|
async publish(channel: string, message: string): Promise<number> {
|
||||||
|
return await this.client.publish(channel, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
disconnect(): void {
|
disconnect(): void {
|
||||||
if (this.isConnected) {
|
if (this.isConnected) {
|
||||||
this.client.disconnect();
|
this.client.disconnect();
|
||||||
}
|
}
|
||||||
|
if (this.subscriberClient) {
|
||||||
|
this.subscriberClient.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isClientConnected(): boolean {
|
get isClientConnected(): boolean {
|
||||||
|
|
|
||||||
44
apps/backend/src/lib/telegram-service.ts
Normal file
44
apps/backend/src/lib/telegram-service.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const BOT_TOKEN = '8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U';
|
||||||
|
// const CHAT_IDS = ['5147700658', '-5075171894'];
|
||||||
|
const CHAT_IDS = [ '-5075171894'];
|
||||||
|
const TELEGRAM_API_URL = `https://api.telegram.org/bot${BOT_TOKEN}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to Telegram bot
|
||||||
|
* @param message The message text to send
|
||||||
|
* @returns Promise<boolean> indicating success or failure
|
||||||
|
*/
|
||||||
|
export const sendTelegramMessage = async (message: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const results = await Promise.all(
|
||||||
|
CHAT_IDS.map(async (chatId) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`${TELEGRAM_API_URL}/sendMessage`, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: message,
|
||||||
|
parse_mode: 'HTML',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.ok) {
|
||||||
|
console.log(`Telegram message sent successfully to ${chatId}`);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.error(`Telegram API error for ${chatId}:`, response.data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to send Telegram message to ${chatId}:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return true if at least one message was sent successfully
|
||||||
|
return results.some((result) => result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to send Telegram message:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
import { RazorpayPaymentService } from "../../lib/payments-utils";
|
import { RazorpayPaymentService } from "../../lib/payments-utils";
|
||||||
import { getNextDeliveryDate } from "../common-apis/common";
|
import { getNextDeliveryDate } from "../common-apis/common";
|
||||||
import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store";
|
import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store";
|
||||||
|
import { publishFormattedOrder } from "../../lib/post-order-handler";
|
||||||
|
|
||||||
|
|
||||||
const validateAndGetCoupon = async (
|
const validateAndGetCoupon = async (
|
||||||
|
|
@ -365,6 +366,9 @@ const placeOrderUtil = async (params: {
|
||||||
sendOrderPlacedNotification(userId, order.id.toString());
|
sendOrderPlacedNotification(userId, order.id.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publish order details to Redis Pub/Sub
|
||||||
|
await publishFormattedOrder(createdOrders, ordersBySlot);
|
||||||
|
|
||||||
return { success: true, data: createdOrders };
|
return { success: true, data: createdOrders };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
||||||
// const BASE_API_URL = API_URL;
|
// const BASE_API_URL = API_URL;
|
||||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
||||||
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
||||||
const BASE_API_URL = 'http://192.168.1.7:4000';
|
// const BASE_API_URL = 'http://192.168.1.7:4000';
|
||||||
// let BASE_API_URL = "https://mf.freshyo.in";
|
let BASE_API_URL = "https://mf.freshyo.in";
|
||||||
// let BASE_API_URL = 'http://192.168.100.103:4000';
|
// let BASE_API_URL = 'http://192.168.100.103:4000';
|
||||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue