import express from 'express'; import cloudinary from '../config/cloudinary.js'; import User from '../models/User.js'; import { updateKarma } from '../services/KarmaService.js'; import CallSession from '../models/CallSession.js'; import SupportTicket from '../models/SupportTicket.js'; import { requireAdmin } from '../middleware/requireAdmin.js'; import axios from 'axios'; const router = express.Router(); // Middleware to check auth const requireAuth = (req, res, next) => { if (!req.user) return res.status(401).send({ error: 'You must log in!' }); next(); }; // 1. Get Cloudinary Signature (Secure Upload) router.get('/upload-signature', requireAuth, (req, res) => { if ( !process.env.CLOUDINARY_CLOUD_NAME || !process.env.CLOUDINARY_API_KEY || !process.env.CLOUDINARY_API_SECRET ) { return res.status(500).json({ error: 'Cloudinary environment variables not configured' }); } const timestamp = Math.round((new Date()).getTime() / 1000); const signature = cloudinary.utils.api_sign_request({ timestamp: timestamp, folder: 'cragy_profiles', }, process.env.CLOUDINARY_API_SECRET); res.json({ signature, timestamp, cloudName: process.env.CLOUDINARY_CLOUD_NAME, apiKey: process.env.CLOUDINARY_API_KEY }); }); // 2. Update Profile (Finalize Onboarding) router.post('/update-profile', requireAuth, async (req, res) => { try { const user = await User.findById(req.user._id); if (!user) return res.status(404).send({ error: 'User not found' }); const { gender, college, city, relationshipStatus, photos, bio, socials } = req.body; // ✅ Update ONLY provided fields if (gender !== undefined) user.profile.gender = gender; if (college !== undefined) user.profile.college = college; if (city !== undefined) user.profile.city = city; if (relationshipStatus !== undefined) user.profile.relationshipStatus = relationshipStatus; if (photos !== undefined) user.profile.photos = photos; if (bio !== undefined) user.profile.bio = bio; if (socials !== undefined) user.profile.socials = socials; // ✅ Only enforce completion rules once if (!user.profile.isComplete) { if (!user.profile.gender || !user.profile.college || !user.profile.photos?.length) { return res.status(400).send({ error: 'Missing required onboarding fields' }); } user.profile.isComplete = true; } await user.save(); res.send(user); } catch (err) { console.error(err); res.status(500).send({ error: 'Update failed' }); } }); // 3. Rate a User (Post-Call) router.post('/rate', requireAuth, async (req, res) => { const { targetUserId, rating } = req.body; // rating: 'UP' or 'DOWN' if (!targetUserId || !rating) return res.status(400).send({ error: 'Invalid data' }); // Prevent rating yourself if (req.user._id.toString() === targetUserId) { return res.status(400).send({ error: "Cannot rate yourself" }); } try { if (rating === 'UP') { await updateKarma(targetUserId, 1, 'Good Conversation'); } else if (rating === 'DOWN') { await updateKarma(targetUserId, -5, 'Bad Experience'); } res.send({ success: true }); } catch (err) { res.status(500).send({ error: 'Rating failed' }); } }); // 4. Get Missed Connection router.get('/recent-match', requireAuth, async (req, res) => { try { // Find the most recent session involving this user that ended < 5 minutes ago const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); const lastSession = await CallSession.findOne({ participants: req.user._id, endTime: { $gte: fiveMinutesAgo } }) .sort({ endTime: -1 }) .populate('participants', 'displayName profile.photos'); // Get partner details if (!lastSession) return res.send(null); // Identify the partner const partner = lastSession.participants.find(p => !p._id.equals(req.user._id)); res.send({ sessionId: lastSession._id, partner: { _id: partner._id, name: partner.displayName, photo: partner.profile.photos[0] } }); } catch (err) { res.status(500).send({ error: 'Fetch failed' }); } }); // 5. Contact Support router.post('/contact', async (req, res) => { const { email, message, category } = req.body; if (!email || !message) { return res.status(400).send({ error: 'Missing required fields' }); } try { const ticket = await SupportTicket.create({ email, message, category, user: req.user?._id || null, }); console.log(`📩 Support ticket saved: ${ticket._id}`); res.send({ success: true }); } catch (err) { console.error('Support ticket failed', err); res.status(500).send({ error: 'Failed to submit ticket' }); } }); // 6. Get TURN Credentials router.get('/turn-credentials', requireAuth, async (req, res) => { try { // 1. Ask Metered for credentials using your Secret Key const response = await axios.get(`https://www.metered.ca/api/v1/turn/credentials?apiKey=${process.env.METERED_API_KEY}`); // 2. Send the credentials to the frontend res.send(response.data); } catch (err) { console.error("TURN Fetch Error:", err); // Fallback to just Google if Metered fails res.send([ { urls: "stun:stun.l.google.com:19302" } ]); } }); // ADMIN: Get all support tickets router.get( '/admin/support-tickets', requireAuth, requireAdmin, async (req, res) => { try { const tickets = await SupportTicket.find() .sort({ createdAt: -1 }) .populate('user', 'displayName email'); res.send(tickets); } catch (err) { res.status(500).send({ error: 'Failed to load tickets' }); } } ); // ADMIN: Update ticket status router.post( '/admin/support-tickets/:id/status', requireAuth, requireAdmin, async (req, res) => { const { status } = req.body; if (!['open', 'resolved'].includes(status)) { return res.status(400).send({ error: 'Invalid status' }); } try { const ticket = await SupportTicket.findByIdAndUpdate( req.params.id, { status }, { new: true } ); res.send(ticket); } catch (err) { res.status(500).send({ error: 'Failed to update ticket' }); } } ); export default router;