375 lines
15 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.videosRoutes = videosRoutes;
const database_1 = require("../../../config/database");
const auth_1 = require("../middleware/auth");
const zod_1 = require("zod");
const promises_1 = require("fs/promises");
const thumbnail_service_1 = require("../services/thumbnail.service");
const logger_1 = require("../../../utils/logger");
async function videosRoutes(fastify) {
// List videos (admin only)
fastify.get('/', {
preHandler: auth_1.requireAdminRole,
}, async (request, reply) => {
const limit = parseInt(request.query.limit || '50');
const offset = parseInt(request.query.offset || '0');
const search = request.query.search;
const orientation = request.query.orientation;
const producers = request.query.producers?.split(',').filter(Boolean);
const isShort = request.query.isShort;
// Build Prisma WHERE clause
const where = {};
if (isShort !== undefined) {
where.isShort = isShort === 'true';
}
if (search) {
where.title = {
contains: search,
mode: 'insensitive',
};
}
if (orientation) {
where.orientation = orientation;
}
if (producers && producers.length > 0) {
where.producer = {
in: producers,
};
}
const videos = await database_1.prisma.video.findMany({
where,
select: {
id: true,
title: true,
filename: true,
durationSeconds: true,
fileSize: true,
width: true,
height: true,
orientation: true,
producer: true,
thumbnailPath: true,
createdAt: true,
isPublished: true,
publishedAt: true,
scheduledPublishAt: true,
scheduledUnpublishAt: true,
category: true,
isShort: true,
accessLevel: true,
},
orderBy: {
createdAt: 'desc',
},
take: limit,
skip: offset,
});
// Get total count
const total = await database_1.prisma.video.count({ where });
// Map videos to include thumbnailUrl
const videosWithThumbnails = videos.map((video) => ({
...video,
duration: video.durationSeconds ?? 0, // Add duration alias for frontend
thumbnailUrl: video.thumbnailPath ? `/media/videos/${video.id}/thumbnail` : null,
}));
return {
videos: videosWithThumbnails,
total,
limit,
offset,
};
});
// Get single video (admin only for now)
fastify.get('/:id', {
preHandler: auth_1.requireAdminRole,
}, async (request, reply) => {
const videoId = parseInt(request.params.id);
const video = await database_1.prisma.video.findUnique({
where: { id: videoId },
});
if (!video) {
return reply.code(404).send({ message: 'Video not found' });
}
return {
video: {
...video,
duration: video.durationSeconds ?? 0,
thumbnailUrl: video.thumbnailPath ? `/media/videos/${video.id}/thumbnail` : null,
},
};
});
// Get list of producers (admin only)
fastify.get('/producers', {
preHandler: auth_1.requireAdminRole,
}, async (request, reply) => {
const videos = await database_1.prisma.video.findMany({
where: {
producer: { not: null },
},
select: {
producer: true,
},
distinct: ['producer'],
});
return videos.map((v) => v.producer).filter(Boolean);
});
// Health check for videos routes
fastify.get('/health', async (request, reply) => {
// Test database connection by counting videos
const count = await database_1.prisma.video.count();
return {
status: 'ok',
videosCount: count,
};
});
// ========================================================================
// PUBLISHING ROUTES (replaces copy-to-public)
// ========================================================================
// Zod schemas for publishing
const PublishSchema = zod_1.z.object({
category: zod_1.z.enum(['videos', 'curated', 'compilations', 'playback', 'highlights']),
});
const BulkPublishSchema = zod_1.z.object({
videoIds: zod_1.z.array(zod_1.z.number().int().positive()).min(1).max(100),
category: zod_1.z.enum(['videos', 'curated', 'compilations', 'playback', 'highlights']),
});
const BulkUnpublishSchema = zod_1.z.object({
videoIds: zod_1.z.array(zod_1.z.number().int().positive()).min(1).max(100),
});
// POST /videos/:id/publish - Publish single video
fastify.post('/:id/publish', { preHandler: auth_1.requireAdminRole }, async (request, reply) => {
const videoId = parseInt(request.params.id);
const parseResult = PublishSchema.safeParse(request.body);
if (!parseResult.success) {
return reply.code(400).send({ message: 'Invalid category', errors: parseResult.error.errors });
}
const { category } = parseResult.data;
try {
const video = await database_1.prisma.video.update({
where: { id: videoId },
data: {
isPublished: true,
publishedAt: new Date(),
category,
},
});
logger_1.logger.info(`Video ${videoId} published to ${category}`);
return { success: true, video };
}
catch (error) {
logger_1.logger.error(`Error publishing video ${videoId}:`, error);
return reply.code(500).send({ message: 'Failed to publish video', error: error.message });
}
});
// POST /videos/:id/unpublish - Unpublish single video
fastify.post('/:id/unpublish', { preHandler: auth_1.requireAdminRole }, async (request, reply) => {
const videoId = parseInt(request.params.id);
try {
const video = await database_1.prisma.video.update({
where: { id: videoId },
data: {
isPublished: false,
publishedAt: null,
// Keep category for re-publishing
},
});
logger_1.logger.info(`Video ${videoId} unpublished`);
return { success: true, video };
}
catch (error) {
logger_1.logger.error(`Error unpublishing video ${videoId}:`, error);
return reply.code(500).send({ message: 'Failed to unpublish video', error: error.message });
}
});
// POST /videos/bulk-publish - Publish multiple videos
fastify.post('/bulk-publish', { preHandler: auth_1.requireAdminRole }, async (request, reply) => {
const parseResult = BulkPublishSchema.safeParse(request.body);
if (!parseResult.success) {
return reply.code(400).send({ message: 'Invalid request', errors: parseResult.error.errors });
}
const { videoIds, category } = parseResult.data;
try {
const result = await database_1.prisma.video.updateMany({
where: { id: { in: videoIds } },
data: {
isPublished: true,
publishedAt: new Date(),
category,
},
});
logger_1.logger.info(`Bulk published ${result.count} videos to ${category}`);
return { success: true, count: result.count };
}
catch (error) {
logger_1.logger.error(`Error bulk publishing videos:`, error);
return reply.code(500).send({ message: 'Failed to publish videos', error: error.message });
}
});
// POST /videos/bulk-unpublish - Unpublish multiple videos
fastify.post('/bulk-unpublish', { preHandler: auth_1.requireAdminRole }, async (request, reply) => {
const parseResult = BulkUnpublishSchema.safeParse(request.body);
if (!parseResult.success) {
return reply.code(400).send({ message: 'Invalid request', errors: parseResult.error.errors });
}
const { videoIds } = parseResult.data;
try {
const result = await database_1.prisma.video.updateMany({
where: { id: { in: videoIds } },
data: {
isPublished: false,
publishedAt: null,
},
});
logger_1.logger.info(`Bulk unpublished ${result.count} videos`);
return { success: true, count: result.count };
}
catch (error) {
logger_1.logger.error(`Error bulk unpublishing videos:`, error);
return reply.code(500).send({ message: 'Failed to unpublish videos', error: error.message });
}
});
// POST /videos/:id/lock - Lock published video
fastify.post('/:id/lock', { preHandler: auth_1.requireAdminRole }, async (request, reply) => {
const videoId = parseInt(request.params.id);
const userId = request.user?.id;
try {
const video = await database_1.prisma.video.update({
where: { id: videoId },
data: {
isLocked: true,
lockedAt: new Date(),
lockedById: userId,
},
});
logger_1.logger.info(`Video ${videoId} locked by user ${userId}`);
return { success: true, video };
}
catch (error) {
logger_1.logger.error(`Error locking video ${videoId}:`, error);
return reply.code(500).send({ message: 'Failed to lock video', error: error.message });
}
});
// POST /videos/:id/unlock - Unlock published video
fastify.post('/:id/unlock', { preHandler: auth_1.requireAdminRole }, async (request, reply) => {
const videoId = parseInt(request.params.id);
try {
const video = await database_1.prisma.video.update({
where: { id: videoId },
data: {
isLocked: false,
lockedAt: null,
lockedById: null,
},
});
logger_1.logger.info(`Video ${videoId} unlocked`);
return { success: true, video };
}
catch (error) {
logger_1.logger.error(`Error unlocking video ${videoId}:`, error);
return reply.code(500).send({ message: 'Failed to unlock video', error: error.message });
}
});
// ========================================================================
// THUMBNAIL GENERATION ROUTES
// ========================================================================
// POST /videos/:id/generate-thumbnail - Generate thumbnail for single video
fastify.post('/:id/generate-thumbnail', { preHandler: auth_1.requireAdminRole }, async (request, reply) => {
const videoId = parseInt(request.params.id);
try {
const video = await database_1.prisma.video.findUnique({
where: { id: videoId },
});
if (!video) {
return reply.code(404).send({ message: 'Video not found' });
}
// Check if video file exists
try {
await (0, promises_1.access)(video.path);
}
catch {
return reply.code(400).send({ message: 'Video file not found on disk' });
}
// Generate thumbnail
const thumbnailPath = await thumbnail_service_1.ThumbnailService.generateThumbnail({
videoPath: video.path,
videoId: video.id,
duration: video.durationSeconds ?? 0,
orientation: video.orientation ?? '',
});
// Update video with thumbnail path
const updatedVideo = await database_1.prisma.video.update({
where: { id: videoId },
data: { thumbnailPath },
});
logger_1.logger.info(`Thumbnail generated for video ${videoId}`);
return { success: true, video: updatedVideo };
}
catch (error) {
logger_1.logger.error(`Error generating thumbnail for video ${videoId}:`, error);
return reply.code(500).send({ message: 'Failed to generate thumbnail', error: error.message });
}
});
// POST /videos/bulk-generate-thumbnails - Generate thumbnails for all videos without them
fastify.post('/bulk-generate-thumbnails', { preHandler: auth_1.requireAdminRole }, async (request, reply) => {
try {
// Find all videos without thumbnails
const videos = await database_1.prisma.video.findMany({
where: {
thumbnailPath: null,
isValid: true,
},
select: {
id: true,
path: true,
durationSeconds: true,
orientation: true,
},
});
logger_1.logger.info(`Found ${videos.length} videos without thumbnails`);
const results = {
total: videos.length,
succeeded: 0,
failed: 0,
errors: [],
};
for (const video of videos) {
try {
// Check if video file exists
await (0, promises_1.access)(video.path);
// Generate thumbnail
const thumbnailPath = await thumbnail_service_1.ThumbnailService.generateThumbnail({
videoPath: video.path,
videoId: video.id,
duration: video.durationSeconds ?? 0,
orientation: video.orientation ?? '',
});
// Update video with thumbnail path
await database_1.prisma.video.update({
where: { id: video.id },
data: { thumbnailPath },
});
results.succeeded++;
logger_1.logger.info(`Thumbnail generated for video ${video.id}`);
}
catch (error) {
results.failed++;
results.errors.push({
videoId: video.id,
error: error.message,
});
logger_1.logger.error(`Failed to generate thumbnail for video ${video.id}:`, error);
}
}
return {
success: true,
results,
};
}
catch (error) {
logger_1.logger.error('Error bulk generating thumbnails:', error);
return reply.code(500).send({ message: 'Failed to bulk generate thumbnails', error: error.message });
}
});
}
//# sourceMappingURL=videos.routes.js.map