369 lines
15 KiB
JavaScript
369 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);
|
|
// Build Prisma WHERE clause
|
|
const where = {};
|
|
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,
|
|
},
|
|
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, // 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,
|
|
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,
|
|
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,
|
|
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
|