const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); const app = express(); app.use(cors()); app.use(express.json()); // MongoDB Connection - FIXED URI with correct cluster ID const MONGO_URI = "mongodb+srv://ibrohm135:mansur5754@cluster0.intw8qq.mongodb.net/chat_app?retryWrites=true&w=majority"; const connectDB = async () => { try { await mongoose.connect(MONGO_URI); console.log('✅ Connected to MongoDB Atlas'); } catch (err) { console.error('❌ MongoDB Connection Error:', err.message); console.log('🔄 Retrying connection in 5 seconds...'); setTimeout(connectDB, 5000); } }; connectDB(); // --- SCHEMAS --- // Message Schema (Updated with imageUrl, groupId, isRead) const MessageSchema = new mongoose.Schema({ text: String, senderId: String, senderName: String, isAi: Boolean, receiverId: String, isGroup: Boolean, groupId: String, imageUrl: String, isRead: { type: Boolean, default: false }, timestamp: { type: Date, default: Date.now } }); const Message = mongoose.model('Message', MessageSchema); // User Schema (Added username) const UserSchema = new mongoose.Schema({ uid: { type: String, unique: true, required: true }, email: String, displayName: String, username: { type: String, unique: true, sparse: true }, photoURL: String, lastLogin: { type: Date, default: Date.now } }); const User = mongoose.model('User', UserSchema); // Status (Story) Schema const StatusSchema = new mongoose.Schema({ userId: String, userName: String, userImage: String, imageUrl: String, type: { type: String, default: 'image' }, caption: String, color: String, timestamp: { type: Date, default: Date.now }, expiresAt: { type: Date, default: () => Date.now() + 24 * 60 * 60 * 1000 } }); const Status = mongoose.model('Status', StatusSchema); // --- ROUTES --- app.get('/', (req, res) => { res.json({ status: 'running', message: 'Chat App Server - v17 FINAL (Fixed Connection)', endpoints: { messages: 'GET/POST /messages', users: 'POST /users, GET /users/search', status: 'GET/POST /status' } }); }); // Messages app.get('/messages', async (req, res) => { try { const messages = await Message.find().sort({ timestamp: -1 }).limit(100); res.json(messages); } catch (e) { res.status(500).json({ error: e.message }); } }); app.post('/messages', async (req, res) => { try { const { text, senderId, senderName, isAi, receiverId, isGroup, groupId, imageUrl } = req.body; const newMessage = new Message({ text, senderId, senderName, isAi, receiverId, isGroup, groupId, imageUrl }); await newMessage.save(); res.json(newMessage); } catch (e) { res.status(500).json({ error: e.message }); } }); // Get User's Chats (Conversations list with last message and unread count) app.get('/chats/:userId', async (req, res) => { try { const { userId } = req.params; // Find all messages where user is sender or receiver const messages = await Message.find({ $or: [{ senderId: userId }, { receiverId: userId }] }).sort({ timestamp: -1 }); // Group by conversation partner const chatsMap = new Map(); for (const msg of messages) { const partnerId = msg.senderId === userId ? msg.receiverId : msg.senderId; if (!partnerId || partnerId === 'AI') continue; if (!chatsMap.has(partnerId)) { // Get partner info const partner = await User.findOne({ uid: partnerId }); const unreadCount = await Message.countDocuments({ senderId: partnerId, receiverId: userId, isRead: false }); chatsMap.set(partnerId, { id: partnerId, name: partner?.displayName || 'Unknown', image: partner?.photoURL || `https://api.dicebear.com/7.x/initials/png?seed=${partner?.displayName || 'U'}`, lastMessage: msg.text || (msg.imageUrl ? '📷 Photo' : ''), time: msg.timestamp, unread: unreadCount, isOnline: partner?.lastLogin ? (Date.now() - new Date(partner.lastLogin).getTime() < 300000) : false }); } } res.json(Array.from(chatsMap.values())); } catch (e) { res.status(500).json({ error: e.message }); } }); // Mark messages as read app.post('/messages/read', async (req, res) => { try { const { senderId, receiverId } = req.body; await Message.updateMany( { senderId, receiverId, isRead: false }, { isRead: true } ); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); } }); // User Sync app.post('/users', async (req, res) => { try { const { uid, email, displayName, photoURL, username } = req.body; const updateData = { email, displayName, photoURL, lastLogin: new Date() }; if (username) updateData.username = username; const user = await User.findOneAndUpdate( { uid }, updateData, { new: true, upsert: true } ); res.json(user); } catch (e) { res.status(500).json({ error: e.message }); } }); // Search Users app.get('/users/search', async (req, res) => { try { const { q } = req.query; if (!q) return res.json([]); const cleanQ = q.startsWith('@') ? q.substring(1) : q; const users = await User.find({ $or: [ { displayName: { $regex: cleanQ, $options: 'i' } }, { email: { $regex: cleanQ, $options: 'i' } }, { username: { $regex: cleanQ, $options: 'i' } } ] }).limit(20); res.json(users); } catch (e) { res.status(500).json({ error: e.message }); } }); // Status Routes app.get('/status', async (req, res) => { try { const statuses = await Status.find({ expiresAt: { $gt: new Date() } }).sort({ timestamp: -1 }); res.json(statuses); } catch (e) { res.status(500).json({ error: e.message }); } }); app.post('/status', async (req, res) => { try { const { userId, userName, userImage, imageUrl, type, caption, color } = req.body; const newStatus = new Status({ userId, userName, userImage, imageUrl, type, caption, color }); await newStatus.save(); res.json(newStatus); } catch (e) { res.status(500).json({ error: e.message }); } }); // AI Chat Endpoint (using Hugging Face Inference API with Token) const HF_TOKEN = process.env.HF_TOKEN || ''; app.post('/ai', async (req, res) => { try { const { text } = req.body; if (!text) { return res.status(400).json({ error: 'Text is required' }); } // Use Hugging Face Inference API with authentication const response = await fetch('https://api-inference.huggingface.co/models/microsoft/DialoGPT-large', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${HF_TOKEN}` }, body: JSON.stringify({ inputs: text, parameters: { max_length: 150, temperature: 0.7 } }) }); if (response.ok) { const data = await response.json(); // DialoGPT returns generated text let aiText = ''; if (Array.isArray(data) && data[0]?.generated_text) { aiText = data[0].generated_text; } else if (data.generated_text) { aiText = data.generated_text; } else { aiText = "Tushundim. Yana nima so'rashni xohlaysiz?"; } res.json({ text: aiText }); } else { console.error('HF API Error:', response.status, await response.text()); // Fallback response if API fails const fallbackResponses = [ "Salom! Men sizga yordam berishga tayyorman. 😊", "Qiziq savol! Batafsil tushuntirib bera olasizmi?", "Bu juda yaxshi fikr! Davom eting.", "Tushunarli. Yana nima so'rashni xohlaysiz?", "Rahmat! Sizga yana qanday yordam bera olaman?" ]; const randomResponse = fallbackResponses[Math.floor(Math.random() * fallbackResponses.length)]; res.json({ text: randomResponse }); } } catch (e) { console.error('AI Error:', e.message); res.json({ text: "Uzr, texnik xatolik yuz berdi. Keyinroq urinib ko'ring." }); } }); const PORT = process.env.PORT || 7860; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });