Files
ShreejitPanchal 6601501eff Init commit
2026-05-19 19:56:02 +08:00

248 lines
7.2 KiB
TypeScript

import { HttpStatusCode } from '@peertube/peertube-models'
import { isIdValid } from '@server/helpers/custom-validators/misc.js'
import { isVideoFileMimeTypeValid, isVideoImageValid } from '@server/helpers/custom-validators/videos.js'
import { LiveVideoModel, LiveVideoStatus } from '@server/models/video/live-video.js'
import express from 'express'
import { body, param, query } from 'express-validator'
import { areValidationErrors } from './shared/utils.js'
function ensureAdmin (res: express.Response) {
const user = res.locals.oauth?.token?.User
return user?.role === 0
}
async function checkScheduleConflict (req: express.Request, res: express.Response, next: express.NextFunction, excludeId?: number) {
const startTime = new Date(req.body.startTime)
const endTime = new Date(req.body.endTime)
const conflicts = await LiveVideoModel.listConflicts({
targetCamera: req.body.targetCamera,
startTime,
endTime,
excludeId
})
if (conflicts.length !== 0) {
return res.fail({
status: HttpStatusCode.CONFLICT_409,
message: 'Target camera is already assigned to another live video'
})
}
return next()
}
export const listLiveVideosValidator = [
query('status')
.optional()
.isIn(Object.values(LiveVideoStatus))
.withMessage('Invalid live video status'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
return next()
}
]
export const getLiveVideoValidator = [
param('id')
.custom(isIdValid)
.withMessage('Invalid live video id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
const liveVideo = await LiveVideoModel.loadById(parseInt(req.params.id))
if (!liveVideo) {
return res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Live video not found'
})
}
;(res.locals as any).liveVideo = liveVideo
return next()
}
]
export const createLiveVideoValidator = [
body('title')
.trim()
.isLength({ min: 1, max: 255 })
.withMessage('Live video title must be between 1 and 255 characters'),
body('startTime')
.isISO8601()
.withMessage('Start time must be a valid ISO 8601 date'),
body('endTime')
.isISO8601()
.withMessage('End time must be a valid ISO 8601 date'),
body('targetCamera')
.trim()
.isLength({ min: 1, max: 255 })
.withMessage('Target camera is required'),
body('thumbnailfile')
.custom((_, { req }) => isVideoImageValid(req.files, 'thumbnailfile'))
.withMessage('Should have a valid thumbnail file'),
body('videofile')
.custom((_, { req }) => isVideoFileMimeTypeValid(req.files))
.withMessage('Should have a valid video file'),
body('subtitlefile')
.optional()
.custom((_, { req }) => {
if (!req.files || !req.files['subtitlefile']) return true
const file = req.files['subtitlefile'][0]
const validMimes = [ 'text/vtt', 'application/x-subrip', 'text/plain' ]
return validMimes.includes(file.mimetype)
})
.withMessage('Should have a valid subtitle file (vtt or srt)'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!ensureAdmin(res)) {
return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Only admins can manage live videos'
})
}
const startTime = new Date(req.body.startTime)
const endTime = new Date(req.body.endTime)
if (endTime <= startTime) {
return res.fail({
status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
message: 'End time must be after start time'
})
}
return checkScheduleConflict(req, res, next)
}
]
export const updateLiveVideoValidator = [
param('id')
.custom(isIdValid)
.withMessage('Invalid live video id'),
body('title')
.optional()
.trim()
.isLength({ min: 1, max: 255 })
.withMessage('Live video title must be between 1 and 255 characters'),
body('startTime')
.optional()
.isISO8601()
.withMessage('Start time must be a valid ISO 8601 date'),
body('endTime')
.optional()
.isISO8601()
.withMessage('End time must be a valid ISO 8601 date'),
body('targetCamera')
.optional()
.trim()
.isLength({ min: 1, max: 255 })
.withMessage('Target camera must be between 1 and 255 characters'),
body('status')
.optional()
.isIn(Object.values(LiveVideoStatus))
.withMessage('Invalid live video status'),
body('thumbnailfile')
.optional()
.custom((_, { req }) => isVideoImageValid(req.files, 'thumbnailfile'))
.withMessage('Should have a valid thumbnail file'),
body('videofile')
.optional()
.custom((_, { req }) => isVideoFileMimeTypeValid(req.files))
.withMessage('Should have a valid video file'),
body('subtitlefile')
.optional()
.custom((_, { req }) => {
if (!req.files || !req.files['subtitlefile']) return true
const file = req.files['subtitlefile'][0]
const validMimes = [ 'text/vtt', 'application/x-subrip', 'text/plain' ]
return validMimes.includes(file.mimetype)
})
.withMessage('Should have a valid subtitle file (vtt or srt)'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!ensureAdmin(res)) {
return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Only admins can manage live videos'
})
}
const liveVideo = await LiveVideoModel.loadById(parseInt(req.params.id))
if (!liveVideo) {
return res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Live video not found'
})
}
const startTime = req.body.startTime ? new Date(req.body.startTime) : liveVideo.startTime
const endTime = req.body.endTime ? new Date(req.body.endTime) : liveVideo.endTime
if (endTime <= startTime) {
return res.fail({
status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
message: 'End time must be after start time'
})
}
;(res.locals as any).liveVideo = liveVideo
if (req.body.startTime || req.body.endTime || req.body.targetCamera) {
req.body.startTime = startTime.toISOString()
req.body.endTime = endTime.toISOString()
req.body.targetCamera = req.body.targetCamera ?? liveVideo.targetCamera
return checkScheduleConflict(req, res, next, liveVideo.id)
}
return next()
}
]
export const deleteLiveVideoValidator = [
param('id')
.custom(isIdValid)
.withMessage('Invalid live video id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!ensureAdmin(res)) {
return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Only admins can manage live videos'
})
}
const liveVideo = await LiveVideoModel.loadById(parseInt(req.params.id))
if (!liveVideo) {
return res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Live video not found'
})
}
;(res.locals as any).liveVideo = liveVideo
return next()
}
]