"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.reactionsRoutes = reactionsRoutes; const database_1 = require("../../../config/database"); const auth_1 = require("../middleware/auth"); const chat_stream_routes_js_1 = require("./chat-stream.routes.js"); // Rebranded reaction emojis (6 standard social reactions) const REACTION_EMOJIS = { like: '👍', love: '❤️', laugh: '😂', wow: '😮', sad: '😢', angry: '😠', }; // Cooldown tracking: userId+type -> lastTimestamp const reactionCooldowns = new Map(); const COOLDOWN_SECONDS = 30; // Format video timestamp as MM:SS or H:MM:SS function formatVideoTime(seconds) { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; if (h > 0) { return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; } return `${m}:${s.toString().padStart(2, '0')}`; } async function reactionsRoutes(fastify) { // Add reaction (authenticated users only) fastify.post('/', { preHandler: auth_1.authenticate, }, async (request, reply) => { const { mediaId, reactionType, videoTimestamp } = request.body; const userId = request.user.id; // Validate reaction type if (!REACTION_EMOJIS[reactionType]) { return reply.code(400).send({ message: 'Invalid reaction type' }); } // Check cooldown const cooldownKey = `${userId}:${reactionType}`; const now = Date.now(); const lastReactionTime = reactionCooldowns.get(cooldownKey); if (lastReactionTime) { const timeSinceLastReaction = (now - lastReactionTime) / 1000; if (timeSinceLastReaction < COOLDOWN_SECONDS) { const cooldownRemaining = Math.ceil(COOLDOWN_SECONDS - timeSinceLastReaction); return reply.code(429).send({ message: `Please wait ${cooldownRemaining}s before reacting again`, cooldownEndsAt: new Date(lastReactionTime + COOLDOWN_SECONDS * 1000).toISOString(), }); } } // Check if video exists const video = await database_1.prisma.video.findUnique({ where: { id: mediaId }, }); if (!video) { return reply.code(404).send({ message: 'Video not found' }); } // Update cooldown reactionCooldowns.set(cooldownKey, now); // Create reaction const reaction = await database_1.prisma.videoReaction.create({ data: { mediaId, userId, reactionType: reactionType, // Cast string to ReactionType enum videoTimestamp, createdAt: new Date(), }, include: { user: { select: { id: true, name: true, email: true, }, }, }, }); // Broadcast to SSE subscribers const broadcastData = { id: reaction.id, reactionType: reaction.reactionType, emoji: REACTION_EMOJIS[reactionType], videoTimestamp: reaction.videoTimestamp, formattedTime: formatVideoTime(videoTimestamp), createdAt: reaction.createdAt.toISOString(), user: reaction.user ? { id: reaction.user.id, name: reaction.user.name || reaction.user.email, } : null, }; (0, chat_stream_routes_js_1.broadcastReactionToVideo)(mediaId, broadcastData); return { success: true, reaction: broadcastData, }; }); // Get reactions fastify.get('/', async (request, reply) => { const { mediaId, userId, limit = '50' } = request.query; const where = {}; // Filter by mediaId if provided if (mediaId) { where.mediaId = parseInt(mediaId); } // Filter by userId if provided if (userId) { where.userId = userId; } const reactions = await database_1.prisma.videoReaction.findMany({ where, orderBy: { createdAt: 'desc', }, take: parseInt(limit), }); // Enhance with emojis and formatted times const enhancedReactions = reactions.map(r => ({ ...r, emoji: REACTION_EMOJIS[r.reactionType] || '❓', formattedTime: formatVideoTime(r.videoTimestamp), })); return { reactions: enhancedReactions, }; }); // Get reaction config (returns available reactions) fastify.get('/config', async (request, reply) => { return { reactions: Object.entries(REACTION_EMOJIS).map(([type, emoji]) => ({ type, emoji, label: type.charAt(0).toUpperCase() + type.slice(1), })), }; }); // Get reactions for chat timeline (non-aggregated, for display in chat) fastify.get('/:mediaId/chat', async (request, reply) => { const mediaId = parseInt(request.params.mediaId, 10); const limit = parseInt(request.query.limit || '500', 10); if (isNaN(mediaId)) { return reply.code(400).send({ message: 'Invalid media ID' }); } const reactions = await database_1.prisma.videoReaction.findMany({ where: { mediaId }, include: { user: { select: { id: true, name: true, email: true, }, }, }, orderBy: { createdAt: 'asc', }, take: limit, }); // Transform for chat timeline const transformedReactions = reactions.map((r) => ({ id: r.id, type: 'reaction', reactionType: r.reactionType, emoji: REACTION_EMOJIS[r.reactionType] || '❓', videoTimestamp: r.videoTimestamp, formattedTime: formatVideoTime(r.videoTimestamp), createdAt: r.createdAt.toISOString(), user: r.user ? { id: r.user.id, name: r.user.name || r.user.email, } : null, })); return { reactions: transformedReactions, }; }); /** * Cleanup cooldown map periodically (every 5 minutes) */ setInterval(() => { const now = Date.now(); for (const [key, timestamp] of reactionCooldowns.entries()) { if (now - timestamp > COOLDOWN_SECONDS * 1000) { reactionCooldowns.delete(key); } } }, 5 * 60 * 1000); } //# sourceMappingURL=reactions.routes.js.map