diff --git a/apps/backend/src/lib/init.ts b/apps/backend/src/lib/init.ts index f69fbca..69a8370 100755 --- a/apps/backend/src/lib/init.ts +++ b/apps/backend/src/lib/init.ts @@ -1,11 +1,13 @@ import './notif-job'; import { initializeAllStores } from '../stores/store-initializer'; +import { startOrderHandler, publishOrder } from './post-order-handler'; /** * Initialize all application services * This function handles initialization of: * - Role Manager (fetches and caches all roles) * - Const Store (syncs constants from DB to Redis) + * - Post Order Handler (Redis Pub/Sub subscriber) * - Other services can be added here in the future */ export const initFunc = async (): Promise => { @@ -18,6 +20,25 @@ export const initFunc = async (): Promise => { // Notification queue and worker are initialized via import 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'); } catch (error) { console.error('Application initialization failed:', error); diff --git a/apps/backend/src/lib/post-order-handler.ts b/apps/backend/src/lib/post-order-handler.ts new file mode 100644 index 0000000..281b7bd --- /dev/null +++ b/apps/backend/src/lib/post-order-handler.ts @@ -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 = 'šŸ›’ New Order Placed\n\n'; + + orderData.orders.forEach((order, index) => { + if (orderData.orders.length > 1) { + message += `Order ${index + 1}\n`; + } + + message += 'šŸ“¦ Items:\n'; + order.orderItems.forEach(item => { + message += ` • ${item.productName} x${item.quantity}\n`; + }); + + message += `\nšŸ’° Total: ₹${order.totalAmount}\n`; + message += `🚚 Delivery: ${new Date(order.deliveryTime).toLocaleString()}\n`; + message += `ā° Ordered: ${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 => { + 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 => { + 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 => { + 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 indicating success or failure + */ +export const publishFormattedOrder = async ( + createdOrders: any[], + ordersBySlot: Map +): Promise => { + 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; + } +}; diff --git a/apps/backend/src/lib/redis-client.ts b/apps/backend/src/lib/redis-client.ts index a4937cb..87953e8 100644 --- a/apps/backend/src/lib/redis-client.ts +++ b/apps/backend/src/lib/redis-client.ts @@ -3,6 +3,7 @@ import { redisUrl } from './env-exporter'; class RedisClient { private client: RedisClientType; + private subscriberClient: RedisClientType | null = null; private isConnected: boolean = false; constructor() { @@ -71,10 +72,48 @@ class RedisClient { return await this.client.MGET(keys); } + // Publish message to a channel + async publish(channel: string, message: string): Promise { + return await this.client.publish(channel, message); + } + + // Subscribe to a channel with callback + async subscribe(channel: string, callback: (message: string) => void): Promise { + 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 { + 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(); + } } get isClientConnected(): boolean { @@ -85,4 +124,4 @@ class RedisClient { const redisClient = new RedisClient(); export default redisClient; -export { RedisClient }; \ No newline at end of file +export { RedisClient }; diff --git a/apps/backend/src/lib/telegram-service.ts b/apps/backend/src/lib/telegram-service.ts new file mode 100644 index 0000000..263b472 --- /dev/null +++ b/apps/backend/src/lib/telegram-service.ts @@ -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 indicating success or failure + */ +export const sendTelegramMessage = async (message: string): Promise => { + 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; + } +}; diff --git a/apps/backend/src/trpc/user-apis/order.ts b/apps/backend/src/trpc/user-apis/order.ts index 5ecfd3a..7038db8 100644 --- a/apps/backend/src/trpc/user-apis/order.ts +++ b/apps/backend/src/trpc/user-apis/order.ts @@ -26,6 +26,7 @@ import { import { RazorpayPaymentService } from "../../lib/payments-utils"; import { getNextDeliveryDate } from "../common-apis/common"; import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store"; +import { publishFormattedOrder } from "../../lib/post-order-handler"; const validateAndGetCoupon = async ( @@ -365,6 +366,9 @@ const placeOrderUtil = async (params: { sendOrderPlacedNotification(userId, order.id.toString()); } + // Publish order details to Redis Pub/Sub + await publishFormattedOrder(createdOrders, ordersBySlot); + return { success: true, data: createdOrders }; }; diff --git a/packages/ui/index.ts b/packages/ui/index.ts index 24dd02b..461c3d5 100755 --- a/packages/ui/index.ts +++ b/packages/ui/index.ts @@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone"; // const BASE_API_URL = API_URL; // 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.1.7:4000'; -// let BASE_API_URL = "https://mf.freshyo.in"; +// const BASE_API_URL = 'http://192.168.1.7:4000'; +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.29.176:4000';