const express = require('express'); const router = express.Router(); const Product = require('../models/Product'); const Order = require('../models/Order'); const auth = require('./middleware'); // Public Health Check & Config router.get('/status', (req, res) => { res.json({ status: 'ok', bot: 'active', time: new Date(), apk_url: `${req.protocol}://${req.get('host')}/public/app.apk` // Dynamic APK Link }); }); // --- IMAGE PROXY (To show Telegram images in App) --- const axios = require('axios'); const config = require('../config'); // Need BOT_TOKEN const telegramAgent = require('../utils/telegramAgent'); router.get('/image/:fileId', async (req, res) => { try { const fileId = req.params.fileId; // 1. Get File Path from Telegram const fileRes = await axios.get(`https://api.telegram.org/bot${config.BOT_TOKEN}/getFile?file_id=${fileId}`, { httpsAgent: telegramAgent }); const filePath = fileRes.data.result.file_path; // 2. Stream File to Client const imageUrl = `https://api.telegram.org/file/bot${config.BOT_TOKEN}/${filePath}`; const response = await axios({ url: imageUrl, method: 'GET', responseType: 'stream', httpsAgent: telegramAgent }); response.data.pipe(res); } catch (e) { console.error("Image Proxy Error:", e.message); res.status(404).send('Image not found'); } }); // --- PROTECTED ROUTES --- const Category = require('../models/Category'); const User = require('../models/User'); const Banner = require('../models/Banner'); // --- PROTECTED ROUTES --- router.use(auth); // --- AUTH ROUTES (Phase 13) --- const LoginRequest = require('../models/LoginRequest'); // Check Login Status (Polled by App) router.get('/auth/check', async (req, res) => { try { const { token } = req.query; if (!token) return res.status(400).json({ error: 'Token required' }); const request = await LoginRequest.findOne({ token }); if (request && request.status === 'approved') { // Find real user or create/update? // User checks if user exists in User model: let dbUser = await User.findOne({ id: request.userId }); res.json({ success: true, status: 'approved', user: { id: request.userId, name: request.firstName, username: request.username, language: dbUser ? dbUser.language : null } }); // Ideally delete request after success, but keep for a bit to avoid race conditions or use TTL // await LoginRequest.deleteOne({ token }); } else { res.json({ success: false, status: 'pending' }); } } catch (e) { res.status(500).json({ error: e.message }); } }); // --- FILE UPLOAD (New) --- const fs = require('fs'); // Ensure uploads directory exists const uploadDir = 'uploads/'; if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir); } const multer = require('multer'); const upload = multer({ dest: uploadDir }); const FormData = require('form-data'); router.post('/upload', upload.single('image'), async (req, res) => { try { if (!req.file) return res.status(400).json({ error: 'No file uploaded' }); // Upload to Telegram to get file_id const form = new FormData(); form.append('chat_id', config.ADMIN_IDS[0] || 12345); // Send to admin or dummy chat form.append('document', fs.createReadStream(req.file.path)); // Use a method that returns file_id. sendDocument is good. // NOTE: You need a chat_id where the bot can send messages. // We will try sending to the first ADMIN_ID. const tgRes = await axios.post( `https://api.telegram.org/bot${config.BOT_TOKEN}/sendDocument`, form, { headers: form.getHeaders(), httpsAgent: telegramAgent } ); // Cleanup local file fs.unlinkSync(req.file.path); if (tgRes.data && tgRes.data.ok) { const fileId = tgRes.data.result.document.file_id; res.json({ success: true, file_id: fileId }); } else { res.status(500).json({ error: 'Telegram upload failed' }); } } catch (e) { if (req.file) fs.unlinkSync(req.file.path); // Cleanup on error console.error("Upload Error:", e.message); res.status(500).json({ error: e.message }); } }); // --- BANNER ROUTES (New Phase 12) --- // Get Banners router.get('/banners', async (req, res) => { try { const banners = await Banner.find().sort({ createdAt: -1 }); res.json({ success: true, count: banners.length, data: banners }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Add Banner (Admin) router.post('/admin/banners', async (req, res) => { try { const { title, image_url, file_id, color } = req.body; const newBanner = new Banner({ id: Date.now().toString(), title, image_url, file_id, color }); await newBanner.save(); res.json({ success: true, data: newBanner }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Delete Banner (Admin) router.delete('/admin/banners/:id', async (req, res) => { try { await Banner.findOneAndDelete({ id: req.params.id }); res.json({ success: true, message: 'Banner deleted' }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Get Categories (For App Sidebar) router.get('/categories', async (req, res) => { try { const categories = await Category.find(); res.json({ success: true, count: categories.length, data: categories }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Admin Stats (For App Admin Dashboard) router.get('/admin/stats', async (req, res) => { try { const userCount = await User.countDocuments(); const productCount = await Product.countDocuments(); const orderCount = await Order.countDocuments(); // Calculate Revenue & Chart Data const orders = await Order.find({ status: 'completed' }); const revenue = orders.reduce((sum, o) => sum + o.total, 0); // Chart Data (Last 7 Days) const chartData = {}; const today = new Date(); for (let i = 6; i >= 0; i--) { const d = new Date(today); d.setDate(today.getDate() - i); const dateStr = d.toISOString().split('T')[0]; chartData[dateStr] = 0; } orders.forEach(order => { // Assuming order.createdAt is a Date object or string const dateStr = new Date(order.createdAt).toISOString().split('T')[0]; if (chartData[dateStr] !== undefined) { chartData[dateStr] += order.total; } }); // Convert to array for Flutter const chartArray = Object.keys(chartData).map(date => ({ date, amount: chartData[date] })); res.json({ success: true, data: { users: userCount, products: productCount, orders: orderCount, revenue: revenue, chart: chartArray } }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Get All Products (with Filter, Search & Sort) router.get('/products', async (req, res) => { try { const query = {}; // Category Filter if (req.query.category && req.query.category !== 'Hammasi') { query.category = req.query.category; } // Search Filter if (req.query.search) { query.name = { $regex: req.query.search, $options: 'i' }; } // Price Range Filter if (req.query.minPrice || req.query.maxPrice) { query.price = {}; if (req.query.minPrice) query.price.$gte = Number(req.query.minPrice); if (req.query.maxPrice) query.price.$lte = Number(req.query.maxPrice); } let sortOption = {}; if (req.query.sort) { switch (req.query.sort) { case 'price_asc': sortOption = { price: 1 }; break; case 'price_desc': sortOption = { price: -1 }; break; case 'rating': sortOption = { 'reviews.rating': -1 }; break; // Simple sort, ideally avg default: sortOption = { createdAt: -1 }; } } const products = await Product.find(query).sort(sortOption); res.json({ success: true, count: products.length, data: products }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Add Review to Product router.post('/products/:id/reviews', async (req, res) => { try { const { userId, userName, rating, comment } = req.body; const product = await Product.findOne({ id: req.params.id }); if (!product) return res.status(404).json({ error: 'Product not found' }); const review = { userId: userId || 'anon', userName: userName || 'Anonim', rating: Number(rating), comment, date: new Date() }; product.reviews.push(review); await product.save(); res.json({ success: true, message: 'Review added', data: product }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Get User Orders router.get('/orders/:userId', async (req, res) => { try { const orders = await Order.find({ userId: req.params.userId }).sort({ createdAt: -1 }); res.json({ success: true, count: orders.length, data: orders }); } catch (e) { res.status(500).json({ error: e.message }); } }); // ADMIN: Get All Orders router.get('/admin/orders', async (req, res) => { try { const orders = await Order.find().sort({ createdAt: -1 }); res.json({ success: true, count: orders.length, data: orders }); } catch (e) { res.status(500).json({ error: e.message }); } }); // ADMIN: Update Order Status router.put('/admin/orders/:id', async (req, res) => { try { const { status } = req.body; const order = await Order.findOneAndUpdate( { id: req.params.id }, { status: status }, { new: true } ); if (!order) return res.status(404).json({ error: 'Order not found' }); // Trigger Push Notification const statusMessages = { 'accepted': 'Buyurtmangiz qabul qilindi! ✅', 'shipping': 'Buyurtmangiz yo\'lga chiqdi! 🚚', 'completed': 'Buyurtma yetkazildi. Xaridingiz uchun rahmat! 🎉', 'cancelled': 'Buyurtmangiz bekor qilindi. ❌' }; if (statusMessages[status]) { sendPushNotification(order.userId, 'Buyurtma Holati', statusMessages[status]); } res.json({ success: true, data: order }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Create Order (from App) router.post('/orders', async (req, res) => { try { const { userId, items, total, location, phone, userName } = req.body; if (!userId || !items || !total) { return res.status(400).json({ error: 'Missing required fields' }); } const orderId = Date.now(); const newOrder = new Order({ id: orderId, userId, user: userName || 'App User', items, total, phone, location, status: 'new', deliveryMethod: location ? 'delivery' : 'pickup', paymentMethod: 'app_payment' }); await newOrder.save(); // Notify Admins const adminIds = config.ADMIN_IDS; if (adminIds && adminIds.length > 0) { const itemsList = items.map(i => `- ${i.name} x${i.quantity} (${i.price})`).join('\n'); const mapLink = location ? `https://www.google.com/maps?q=${location.latitude},${location.longitude}` : 'Joylashuvsiz'; const message = `🆕 Yangi Buyurtma!\n\n` + `👤 Mijoz: ${userName || 'Noma\'lum'}\n` + `📞 Telefon: ${phone}\n` + `📍 Manzil: ${mapLink}\n\n` + `🛒 Mahsulotlar:\n${itemsList}\n\n` + `💰 Jami: ${total} UZS\n` + `💳 To'lov: ${req.body.paymentMethod || 'Naqd'}\n` + `🆔 ID: ${orderId}`; adminIds.forEach(async (adminId) => { try { await axios.post(`https://api.telegram.org/bot${config.BOT_TOKEN}/sendMessage`, { chat_id: adminId, text: message, parse_mode: 'HTML' }, { httpsAgent: telegramAgent }); } catch (err) { console.error(`Failed to notify admin ${adminId}:`, err.message); } }); } res.json({ success: true, orderId, message: 'Order created' }); } catch (e) { res.status(500).json({ error: e.message }); } }); // --- PUSH NOTIFICATION HELPERS --- const sendPushNotification = async (userId, title, body) => { try { const user = await User.findOne({ id: userId }); if (!user || !user.fcmToken) return; // Note: You need FCM Server Key in config.js (FIREBASE_SERVER_KEY) if (!config.FIREBASE_SERVER_KEY) { console.log('Skipping Push: No FIREBASE_SERVER_KEY in config'); return; } await axios.post('https://fcm.googleapis.com/fcm/send', { to: user.fcmToken, notification: { title: title, body: body, sound: "default" }, data: { click_action: "FLUTTER_NOTIFICATION_CLICK", status: "done" } }, { headers: { 'Content-Type': 'application/json', 'Authorization': `key=${config.FIREBASE_SERVER_KEY}` } }); console.log(`Push sent to ${userId}`); } catch (e) { console.error("Push Error:", e.message); } }; // Update FCM Token router.post('/auth/token', async (req, res) => { try { const { userId, token } = req.body; if (!userId || !token) return res.status(400).json({ error: 'Missing fields' }); await User.findOneAndUpdate({ id: userId }, { fcmToken: token }); res.json({ success: true, message: 'Token updated' }); } catch (e) { res.status(500).json({ error: e.message }); } }); // --- PHONE LOGIN ENDPOINTS --- const OTP_STORE = new Map(); // Store OTPs: "998901234567" -> "1234" router.post('/auth/send-code', async (req, res) => { try { const { phone } = req.body; if (!phone) return res.status(400).json({ success: false, message: "Phone required" }); // Clean phone const cleanPhone = phone.replace('+', ''); // Find user by phone const user = await User.findOne({ phone: cleanPhone }); if (!user) { return res.json({ success: false, status: 'not_found', message: "Ushbu raqam botda ro'yxatdan o'tmagan. Iltimos, botga kiring va raqamingizni yuboring." }); } // Generate Code const code = Math.floor(1000 + Math.random() * 9000).toString(); OTP_STORE.set(cleanPhone, code); // Send via Bot try { await req.bot.telegram.sendMessage(user.id, `🔐 Tasdiqlash kodi: ${code}\n\nIlovaga kiriting.`, { parse_mode: 'HTML' }); res.json({ success: true, status: 'sent', message: "Kod yuborildi" }); } catch (botError) { console.error("Bot Send Error:", botError); res.json({ success: false, message: "Botga yuborib bo'lmadi. Botni bloklamaganmisiz?" }); } } catch (e) { console.error("Send Code Error:", e); res.status(500).json({ success: false, message: "Server Error" }); } }); router.post('/auth/verify-code', async (req, res) => { try { const { phone, code } = req.body; const cleanPhone = phone.replace('+', ''); if (OTP_STORE.get(cleanPhone) === code) { OTP_STORE.delete(cleanPhone); // Consume code const user = await User.findOne({ phone: cleanPhone }); if (!user) return res.status(404).json({ success: false, message: "User not found" }); res.json({ success: true, status: 'approved', user: { id: user.id.toString(), name: user.first_name, username: user.username } }); } else { res.json({ success: false, message: "Kod noto'g'ri" }); } } catch (e) { res.status(500).json({ success: false, message: "Server Error" }); } }); router.post('/auth/firebase', async (req, res) => { try { const { uid, phone, name, email } = req.body; if (!uid) return res.status(400).json({ success: false, message: "UID required" }); let user = await User.findOne({ firebase_uid: uid }); if (!user && phone) { // Try finding by phone if user existed before const cleanPhone = phone.replace('+', ''); user = await User.findOne({ phone: cleanPhone }); } if (!user) { // Create New User user = new User({ id: uid, // Use Firebase UID as ID firebase_uid: uid, first_name: name || 'Foydalanuvchi', phone: phone ? phone.replace('+', '') : null, username: email ? email.split('@')[0] : null }); await user.save(); } else { // Link existing user if (!user.firebase_uid) { user.firebase_uid = uid; await user.save(); } } res.json({ success: true, status: 'approved', user: { id: user.id.toString(), name: user.first_name, username: user.username, phone: user.phone } }); } catch (e) { console.error("Firebase Auth Error:", e); res.status(500).json({ success: false, message: "Server Error" }); } }); module.exports = router;