740 lines
27 KiB
JavaScript
740 lines
27 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.publicMediaRoutes = publicMediaRoutes;
|
|
const database_1 = require("../../../config/database");
|
|
const auth_1 = require("../middleware/auth");
|
|
const logger_1 = require("../../../utils/logger");
|
|
const session_service_1 = require("../services/session.service");
|
|
const public_media_schemas_1 = require("../schemas/public-media.schemas");
|
|
const fs_1 = require("fs");
|
|
const promises_1 = require("fs/promises");
|
|
/**
|
|
* Public Media Gallery API Routes
|
|
* Handles public video listing, upvotes, comments, and admin operations
|
|
*/
|
|
async function publicMediaRoutes(fastify) {
|
|
/**
|
|
* GET /videos (LEGACY ROUTE)
|
|
* Compatibility endpoint for public-media app (port 3100)
|
|
* Converts page-based pagination to offset-based and transforms response format
|
|
*/
|
|
fastify.get('/videos', {
|
|
preHandler: auth_1.optionalAuth,
|
|
}, async (request, reply) => {
|
|
try {
|
|
// Convert page-based to offset-based pagination
|
|
const page = parseInt(request.query.page || '1');
|
|
const limit = parseInt(request.query.limit || '48');
|
|
const offset = (page - 1) * limit;
|
|
const { search, category, sort } = request.query;
|
|
// Check if user is admin
|
|
const ADMIN_ROLES = ['SUPER_ADMIN', 'INFLUENCE_ADMIN', 'MAP_ADMIN'];
|
|
const isAdmin = request.user && ADMIN_ROLES.includes(request.user.role);
|
|
// Build WHERE clause (same logic as /public endpoint)
|
|
const where = {
|
|
isPublished: true, // Only show published videos
|
|
};
|
|
if (!isAdmin) {
|
|
where.isLocked = false;
|
|
}
|
|
if (category) {
|
|
where.category = category;
|
|
}
|
|
if (search) {
|
|
where.filename = {
|
|
contains: search,
|
|
mode: 'insensitive',
|
|
};
|
|
}
|
|
// Determine sort order
|
|
let orderBy = {};
|
|
switch (sort) {
|
|
case 'recent':
|
|
orderBy = { publishedAt: 'desc' };
|
|
break;
|
|
case 'popular':
|
|
orderBy = { upvoteCount: 'desc' };
|
|
break;
|
|
case 'most_viewed':
|
|
orderBy = { viewCount: 'desc' };
|
|
break;
|
|
default:
|
|
orderBy = { publishedAt: 'desc' };
|
|
}
|
|
// Execute queries in parallel
|
|
const [videos, total] = await Promise.all([
|
|
database_1.prisma.video.findMany({
|
|
where,
|
|
select: {
|
|
id: true,
|
|
filename: true,
|
|
category: true,
|
|
durationSeconds: true,
|
|
quality: true,
|
|
orientation: true,
|
|
thumbnailPath: true,
|
|
fileSize: true,
|
|
viewCount: true,
|
|
upvoteCount: true,
|
|
commentCount: true,
|
|
createdAt: true,
|
|
},
|
|
orderBy,
|
|
take: limit,
|
|
skip: offset,
|
|
}),
|
|
database_1.prisma.video.count({ where }),
|
|
]);
|
|
// Calculate total pages
|
|
const totalPages = Math.ceil(total / limit);
|
|
// Transform to legacy format expected by public-media app
|
|
return {
|
|
data: videos.map((video) => ({
|
|
id: video.id.toString(), // int → string
|
|
title: video.filename.replace(/\.[^.]+$/, ''), // filename without extension
|
|
fileName: video.filename, // camelCase
|
|
fileSize: Number(video.fileSize || 0), // BigInt → number
|
|
duration: video.durationSeconds || 0, // rename field
|
|
width: 0, // not stored, placeholder
|
|
height: 0, // not stored, placeholder
|
|
orientation: video.orientation || 'horizontal',
|
|
category: video.category,
|
|
viewCount: video.viewCount || 0,
|
|
createdAt: video.createdAt.toISOString(),
|
|
updatedAt: video.createdAt.toISOString(), // no updatedAt field, use createdAt
|
|
thumbnailUrl: video.thumbnailPath ? `/api/media/public/${video.id}/thumbnail` : undefined,
|
|
streamUrl: `/media/public/${video.category}/${video.filename}`, // static file path
|
|
})),
|
|
total,
|
|
page,
|
|
limit,
|
|
totalPages,
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error fetching videos (legacy endpoint):', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to fetch videos',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* GET /public
|
|
* List public videos with filtering, sorting, and pagination
|
|
*/
|
|
fastify.get('/public', {
|
|
preHandler: auth_1.optionalAuth,
|
|
}, async (request, reply) => {
|
|
try {
|
|
// Validate query params
|
|
const parseResult = public_media_schemas_1.listPublicMediaSchema.safeParse(request.query);
|
|
if (!parseResult.success) {
|
|
return reply.code(400).send({
|
|
message: 'Invalid query parameters',
|
|
errors: parseResult.error.errors,
|
|
});
|
|
}
|
|
const { limit, offset, sort, search, category } = parseResult.data;
|
|
// Check if user is admin
|
|
const ADMIN_ROLES = ['SUPER_ADMIN', 'INFLUENCE_ADMIN', 'MAP_ADMIN'];
|
|
const isAdmin = request.user && ADMIN_ROLES.includes(request.user.role);
|
|
// Build WHERE clause
|
|
const where = {
|
|
isPublished: true, // Only show published videos
|
|
};
|
|
// Non-admins can't see locked videos
|
|
if (!isAdmin) {
|
|
where.isLocked = false;
|
|
}
|
|
// Category filter
|
|
if (category) {
|
|
where.category = category;
|
|
}
|
|
// Search filter (searches filename)
|
|
if (search) {
|
|
where.filename = {
|
|
contains: search,
|
|
mode: 'insensitive',
|
|
};
|
|
}
|
|
// Determine sort order
|
|
let orderBy = {};
|
|
switch (sort) {
|
|
case 'recent':
|
|
orderBy = { publishedAt: 'desc' };
|
|
break;
|
|
case 'popular':
|
|
orderBy = { upvoteCount: 'desc' };
|
|
break;
|
|
case 'most_viewed':
|
|
orderBy = { viewCount: 'desc' };
|
|
break;
|
|
default:
|
|
orderBy = { publishedAt: 'desc' };
|
|
}
|
|
// Execute queries in parallel
|
|
const [videos, total] = await Promise.all([
|
|
database_1.prisma.video.findMany({
|
|
where,
|
|
select: {
|
|
id: true,
|
|
filename: true,
|
|
category: true,
|
|
durationSeconds: true,
|
|
quality: true,
|
|
orientation: true,
|
|
thumbnailPath: true,
|
|
fileSize: true,
|
|
viewCount: true,
|
|
upvoteCount: true,
|
|
commentCount: true,
|
|
createdAt: true,
|
|
isLocked: true,
|
|
position: true,
|
|
publishedAt: true,
|
|
},
|
|
orderBy,
|
|
take: limit,
|
|
skip: offset,
|
|
}),
|
|
database_1.prisma.video.count({ where }),
|
|
]);
|
|
return {
|
|
videos,
|
|
pagination: {
|
|
total,
|
|
limit,
|
|
offset,
|
|
hasMore: offset + limit < total,
|
|
},
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error listing public media:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to list videos',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* GET /public/:id
|
|
* Get single video details
|
|
*/
|
|
fastify.get('/public/:id', {
|
|
preHandler: auth_1.optionalAuth,
|
|
}, async (request, reply) => {
|
|
try {
|
|
const videoId = parseInt(request.params.id);
|
|
if (isNaN(videoId)) {
|
|
return reply.code(400).send({ message: 'Invalid video ID' });
|
|
}
|
|
const video = await database_1.prisma.video.findFirst({
|
|
where: {
|
|
id: videoId,
|
|
isPublished: true, // Only show published videos
|
|
},
|
|
select: {
|
|
id: true,
|
|
filename: true,
|
|
category: true,
|
|
durationSeconds: true,
|
|
quality: true,
|
|
orientation: true,
|
|
thumbnailPath: true,
|
|
fileSize: true,
|
|
viewCount: true,
|
|
upvoteCount: true,
|
|
commentCount: true,
|
|
finishCount: true,
|
|
totalWatchTime: true,
|
|
createdAt: true,
|
|
publishedAt: true,
|
|
isLocked: true,
|
|
position: true,
|
|
uploaderId: true,
|
|
},
|
|
});
|
|
if (!video) {
|
|
return reply.code(404).send({ message: 'Video not found' });
|
|
}
|
|
// Check if locked and user is not admin
|
|
const ADMIN_ROLES = ['SUPER_ADMIN', 'INFLUENCE_ADMIN', 'MAP_ADMIN'];
|
|
const isAdmin = request.user && ADMIN_ROLES.includes(request.user.role);
|
|
if (video.isLocked && !isAdmin) {
|
|
return reply.code(403).send({
|
|
message: 'This video is locked',
|
|
});
|
|
}
|
|
return { video };
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error fetching public media:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to fetch video',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* POST /public/:id/upvote
|
|
* Toggle upvote for a video
|
|
*/
|
|
fastify.post('/public/:id/upvote', async (request, reply) => {
|
|
try {
|
|
const videoId = parseInt(request.params.id);
|
|
if (isNaN(videoId)) {
|
|
return reply.code(400).send({ message: 'Invalid video ID' });
|
|
}
|
|
// Get or create session
|
|
let sessionId;
|
|
try {
|
|
sessionId = await (0, session_service_1.getOrCreateSession)(request);
|
|
}
|
|
catch (error) {
|
|
return reply.code(400).send({
|
|
message: 'Session required',
|
|
error: error.message,
|
|
});
|
|
}
|
|
// Check if video exists and is published
|
|
const video = await database_1.prisma.video.findFirst({
|
|
where: {
|
|
id: videoId,
|
|
isPublished: true,
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (!video) {
|
|
return reply.code(404).send({ message: 'Video not found' });
|
|
}
|
|
// Check if upvote already exists
|
|
const existingUpvote = await database_1.prisma.upvote.findFirst({
|
|
where: {
|
|
mediaId: videoId,
|
|
sessionId,
|
|
},
|
|
});
|
|
if (existingUpvote) {
|
|
// Remove upvote (toggle off)
|
|
await database_1.prisma.$transaction([
|
|
database_1.prisma.upvote.delete({
|
|
where: { id: existingUpvote.id },
|
|
}),
|
|
database_1.prisma.video.update({
|
|
where: { id: videoId },
|
|
data: {
|
|
upvoteCount: {
|
|
decrement: 1,
|
|
},
|
|
},
|
|
}),
|
|
]);
|
|
logger_1.logger.info(`Removed upvote for video ${videoId} from session ${sessionId}`);
|
|
return { upvoted: false };
|
|
}
|
|
else {
|
|
// Add upvote (toggle on)
|
|
await database_1.prisma.$transaction([
|
|
database_1.prisma.upvote.create({
|
|
data: {
|
|
mediaId: videoId,
|
|
sessionId,
|
|
},
|
|
}),
|
|
database_1.prisma.video.update({
|
|
where: { id: videoId },
|
|
data: {
|
|
upvoteCount: {
|
|
increment: 1,
|
|
},
|
|
},
|
|
}),
|
|
]);
|
|
logger_1.logger.info(`Added upvote for video ${videoId} from session ${sessionId}`);
|
|
return { upvoted: true };
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error toggling upvote:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to toggle upvote',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* GET /public/:id/upvote-status
|
|
* Check if current session has upvoted a video
|
|
*/
|
|
fastify.get('/public/:id/upvote-status', async (request, reply) => {
|
|
try {
|
|
const videoId = parseInt(request.params.id);
|
|
if (isNaN(videoId)) {
|
|
return reply.code(400).send({ message: 'Invalid video ID' });
|
|
}
|
|
// Read sessionId from header (don't create if missing)
|
|
const sessionId = request.headers['x-session-id'];
|
|
if (!sessionId) {
|
|
return { upvoted: false };
|
|
}
|
|
// Check if upvote exists
|
|
const upvote = await database_1.prisma.upvote.findFirst({
|
|
where: {
|
|
mediaId: videoId,
|
|
sessionId,
|
|
},
|
|
});
|
|
return { upvoted: !!upvote };
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error checking upvote status:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to check upvote status',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* GET /public/:id/comments
|
|
* List comments for a video
|
|
*/
|
|
fastify.get('/public/:id/comments', async (request, reply) => {
|
|
try {
|
|
const videoId = parseInt(request.params.id);
|
|
const limit = parseInt(request.query.limit || '20');
|
|
const offset = parseInt(request.query.offset || '0');
|
|
if (isNaN(videoId)) {
|
|
return reply.code(400).send({ message: 'Invalid video ID' });
|
|
}
|
|
// Check if video exists and is published
|
|
const video = await database_1.prisma.video.findFirst({
|
|
where: {
|
|
id: videoId,
|
|
isPublished: true,
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (!video) {
|
|
return reply.code(404).send({ message: 'Video not found' });
|
|
}
|
|
// Fetch comments (hide hidden ones)
|
|
const [comments, total] = await Promise.all([
|
|
database_1.prisma.comment.findMany({
|
|
where: {
|
|
mediaId: videoId,
|
|
isHidden: false,
|
|
},
|
|
select: {
|
|
id: true,
|
|
content: true,
|
|
createdAt: true,
|
|
sessionId: true,
|
|
userId: true,
|
|
safetyStatus: true,
|
|
user: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: {
|
|
createdAt: 'desc',
|
|
},
|
|
take: limit,
|
|
skip: offset,
|
|
}),
|
|
database_1.prisma.comment.count({
|
|
where: {
|
|
mediaId: videoId,
|
|
isHidden: false,
|
|
},
|
|
}),
|
|
]);
|
|
return {
|
|
comments,
|
|
pagination: {
|
|
total,
|
|
limit,
|
|
offset,
|
|
hasMore: offset + limit < total,
|
|
},
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error listing comments:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to list comments',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* POST /public/:id/comments
|
|
* Add a comment to a video
|
|
*/
|
|
fastify.post('/public/:id/comments', async (request, reply) => {
|
|
try {
|
|
const videoId = parseInt(request.params.id);
|
|
if (isNaN(videoId)) {
|
|
return reply.code(400).send({ message: 'Invalid video ID' });
|
|
}
|
|
// Validate request body
|
|
const parseResult = public_media_schemas_1.addCommentSchema.safeParse(request.body);
|
|
if (!parseResult.success) {
|
|
return reply.code(400).send({
|
|
message: 'Invalid comment data',
|
|
errors: parseResult.error.errors,
|
|
});
|
|
}
|
|
const { content } = parseResult.data;
|
|
// Get or create session
|
|
let sessionId;
|
|
try {
|
|
sessionId = await (0, session_service_1.getOrCreateSession)(request);
|
|
}
|
|
catch (error) {
|
|
return reply.code(400).send({
|
|
message: 'Session required',
|
|
error: error.message,
|
|
});
|
|
}
|
|
// Check if video exists and is published
|
|
const video = await database_1.prisma.video.findFirst({
|
|
where: {
|
|
id: videoId,
|
|
isPublished: true,
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (!video) {
|
|
return reply.code(404).send({ message: 'Video not found' });
|
|
}
|
|
// Create comment and increment counter in transaction
|
|
const [comment] = await database_1.prisma.$transaction([
|
|
database_1.prisma.comment.create({
|
|
data: {
|
|
mediaId: videoId,
|
|
sessionId,
|
|
userId: request.user?.id || null,
|
|
content,
|
|
safetyStatus: 'pending',
|
|
},
|
|
select: {
|
|
id: true,
|
|
content: true,
|
|
createdAt: true,
|
|
sessionId: true,
|
|
userId: true,
|
|
safetyStatus: true,
|
|
},
|
|
}),
|
|
database_1.prisma.video.update({
|
|
where: { id: videoId },
|
|
data: {
|
|
commentCount: {
|
|
increment: 1,
|
|
},
|
|
},
|
|
}),
|
|
]);
|
|
logger_1.logger.info(`Added comment ${comment.id} for video ${videoId} from session ${sessionId}`);
|
|
return { comment };
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error adding comment:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to add comment',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* GET /public/:id/thumbnail
|
|
* Serve thumbnail image for a video
|
|
*/
|
|
fastify.get('/public/:id/thumbnail', async (request, reply) => {
|
|
try {
|
|
const videoId = parseInt(request.params.id);
|
|
if (isNaN(videoId)) {
|
|
return reply.code(400).send({ message: 'Invalid video ID' });
|
|
}
|
|
// Fetch video with thumbnail path (published only)
|
|
const video = await database_1.prisma.video.findFirst({
|
|
where: {
|
|
id: videoId,
|
|
isPublished: true,
|
|
},
|
|
select: {
|
|
thumbnailPath: true,
|
|
},
|
|
});
|
|
if (!video) {
|
|
return reply.code(404).send({ message: 'Video not found' });
|
|
}
|
|
if (!video.thumbnailPath) {
|
|
return reply.code(404).send({ message: 'Thumbnail not found' });
|
|
}
|
|
// Check if file exists
|
|
try {
|
|
await (0, promises_1.access)(video.thumbnailPath, promises_1.constants.R_OK);
|
|
}
|
|
catch {
|
|
logger_1.logger.warn(`Thumbnail file not found: ${video.thumbnailPath}`);
|
|
return reply.code(404).send({ message: 'Thumbnail file not found' });
|
|
}
|
|
// Stream the file
|
|
const stream = (0, fs_1.createReadStream)(video.thumbnailPath);
|
|
// Set content type based on file extension
|
|
const ext = video.thumbnailPath.toLowerCase().split('.').pop();
|
|
const contentType = ext === 'png' ? 'image/png' : ext === 'webp' ? 'image/webp' : 'image/jpeg';
|
|
return reply.type(contentType).send(stream);
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error serving thumbnail:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to serve thumbnail',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* POST /public/bulk-lock
|
|
* Lock multiple videos (admin only)
|
|
*/
|
|
fastify.post('/public/bulk-lock', {
|
|
preHandler: auth_1.requireAdminRole,
|
|
}, async (request, reply) => {
|
|
try {
|
|
// Validate request body
|
|
const parseResult = public_media_schemas_1.bulkLockSchema.safeParse(request.body);
|
|
if (!parseResult.success) {
|
|
return reply.code(400).send({
|
|
message: 'Invalid request',
|
|
errors: parseResult.error.errors,
|
|
});
|
|
}
|
|
const { ids } = parseResult.data;
|
|
const userId = request.user?.id;
|
|
// Update all videos at once (only published videos)
|
|
const result = await database_1.prisma.video.updateMany({
|
|
where: {
|
|
id: {
|
|
in: ids,
|
|
},
|
|
isPublished: true,
|
|
},
|
|
data: {
|
|
isLocked: true,
|
|
lockedAt: new Date(),
|
|
lockedById: userId,
|
|
},
|
|
});
|
|
logger_1.logger.info(`Locked ${result.count} videos (IDs: ${ids.join(', ')}) by user ${userId}`);
|
|
return {
|
|
success: true,
|
|
count: result.count,
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error bulk locking videos:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to lock videos',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* POST /public/bulk-unlock
|
|
* Unlock multiple videos (admin only)
|
|
*/
|
|
fastify.post('/public/bulk-unlock', {
|
|
preHandler: auth_1.requireAdminRole,
|
|
}, async (request, reply) => {
|
|
try {
|
|
// Validate request body
|
|
const parseResult = public_media_schemas_1.bulkUnlockSchema.safeParse(request.body);
|
|
if (!parseResult.success) {
|
|
return reply.code(400).send({
|
|
message: 'Invalid request',
|
|
errors: parseResult.error.errors,
|
|
});
|
|
}
|
|
const { ids } = parseResult.data;
|
|
const userId = request.user?.id;
|
|
// Update all videos at once (only published videos)
|
|
const result = await database_1.prisma.video.updateMany({
|
|
where: {
|
|
id: {
|
|
in: ids,
|
|
},
|
|
isPublished: true,
|
|
},
|
|
data: {
|
|
isLocked: false,
|
|
lockedAt: null,
|
|
lockedById: null,
|
|
},
|
|
});
|
|
logger_1.logger.info(`Unlocked ${result.count} videos (IDs: ${ids.join(', ')}) by user ${userId}`);
|
|
return {
|
|
success: true,
|
|
count: result.count,
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error bulk unlocking videos:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to unlock videos',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* DELETE /public/:id
|
|
* Unpublish a video from public gallery (admin only)
|
|
* NOTE: This unpublishes instead of deleting - interactions are preserved
|
|
*/
|
|
fastify.delete('/public/:id', {
|
|
preHandler: auth_1.requireAdminRole,
|
|
}, async (request, reply) => {
|
|
try {
|
|
const videoId = parseInt(request.params.id);
|
|
if (isNaN(videoId)) {
|
|
return reply.code(400).send({ message: 'Invalid video ID' });
|
|
}
|
|
// Check if video exists and is published
|
|
const video = await database_1.prisma.video.findFirst({
|
|
where: {
|
|
id: videoId,
|
|
isPublished: true,
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (!video) {
|
|
return reply.code(404).send({ message: 'Video not found' });
|
|
}
|
|
// Unpublish the video (preserves upvotes, comments, views)
|
|
await database_1.prisma.video.update({
|
|
where: { id: videoId },
|
|
data: {
|
|
isPublished: false,
|
|
publishedAt: null,
|
|
// Keep category for re-publishing
|
|
},
|
|
});
|
|
logger_1.logger.info(`Unpublished video ${videoId} by user ${request.user?.id}`);
|
|
return { success: true };
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.error('Error unpublishing video:', error);
|
|
return reply.code(500).send({
|
|
message: 'Failed to unpublish video',
|
|
error: error.message,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
//# sourceMappingURL=public-media.routes.js.map
|