cragy-api / src /routes /userRoutes.js
ShieldX's picture
Update src/routes/userRoutes.js
f941588 verified
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;