197 lines
6.9 KiB
JavaScript
197 lines
6.9 KiB
JavaScript
"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
|