Add video channels
This commit is contained in:
parent
8113a93a0d
commit
72c7248b6f
|
@ -7,7 +7,7 @@ import {
|
|||
setBodyHostPort,
|
||||
remotePodsAddValidator
|
||||
} from '../../../middlewares'
|
||||
import { sendOwnedVideosToPod } from '../../../lib'
|
||||
import { sendOwnedDataToPod } from '../../../lib'
|
||||
import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
|
||||
import { CONFIG } from '../../../initializers'
|
||||
import { PodInstance } from '../../../models'
|
||||
|
@ -43,7 +43,7 @@ function addPods (req: express.Request, res: express.Response, next: express.Nex
|
|||
const pod = db.Pod.build(information)
|
||||
pod.save()
|
||||
.then(podCreated => {
|
||||
return sendOwnedVideosToPod(podCreated.id)
|
||||
return sendOwnedDataToPod(podCreated.id)
|
||||
})
|
||||
.then(() => {
|
||||
return getMyPublicCert()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as express from 'express'
|
||||
import * as Promise from 'bluebird'
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
import { database as db } from '../../../initializers/database'
|
||||
import {
|
||||
|
@ -27,17 +28,28 @@ import {
|
|||
RemoteQaduVideoRequest,
|
||||
RemoteQaduVideoData,
|
||||
RemoteVideoEventRequest,
|
||||
RemoteVideoEventData
|
||||
RemoteVideoEventData,
|
||||
RemoteVideoChannelCreateData,
|
||||
RemoteVideoChannelUpdateData,
|
||||
RemoteVideoChannelRemoveData,
|
||||
RemoteVideoAuthorRemoveData,
|
||||
RemoteVideoAuthorCreateData
|
||||
} from '../../../../shared'
|
||||
|
||||
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
|
||||
|
||||
// Functions to call when processing a remote request
|
||||
// FIXME: use RemoteVideoRequestType as id type
|
||||
const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
|
||||
functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo
|
||||
functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideo
|
||||
functionsHash[ENDPOINT_ACTIONS.ADD_VIDEO] = addRemoteVideoRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.UPDATE_VIDEO] = updateRemoteVideoRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.REMOVE_VIDEO] = removeRemoteVideoRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.ADD_CHANNEL] = addRemoteVideoChannelRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper
|
||||
functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper
|
||||
|
||||
const remoteVideosRouter = express.Router()
|
||||
|
||||
|
@ -133,7 +145,7 @@ function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromP
|
|||
function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return fetchVideoByUUID(eventData.uuid)
|
||||
return fetchVideoByUUID(eventData.uuid, t)
|
||||
.then(videoInstance => {
|
||||
const options = { transaction: t }
|
||||
|
||||
|
@ -196,7 +208,7 @@ function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodI
|
|||
let videoUUID = ''
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid)
|
||||
return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
|
||||
.then(videoInstance => {
|
||||
const options = { transaction: t }
|
||||
|
||||
|
@ -239,22 +251,16 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI
|
|||
.then(video => {
|
||||
if (video) throw new Error('UUID already exists.')
|
||||
|
||||
return undefined
|
||||
return db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t)
|
||||
})
|
||||
.then(() => {
|
||||
const name = videoToCreateData.author
|
||||
const podId = fromPod.id
|
||||
// This author is from another pod so we do not associate a user
|
||||
const userId = null
|
||||
.then(videoChannel => {
|
||||
if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
|
||||
|
||||
return db.Author.findOrCreateAuthor(name, podId, userId, t)
|
||||
})
|
||||
.then(author => {
|
||||
const tags = videoToCreateData.tags
|
||||
|
||||
return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
|
||||
return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoChannel, tagInstances }))
|
||||
})
|
||||
.then(({ author, tagInstances }) => {
|
||||
.then(({ videoChannel, tagInstances }) => {
|
||||
const videoData = {
|
||||
name: videoToCreateData.name,
|
||||
uuid: videoToCreateData.uuid,
|
||||
|
@ -263,7 +269,7 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI
|
|||
language: videoToCreateData.language,
|
||||
nsfw: videoToCreateData.nsfw,
|
||||
description: videoToCreateData.description,
|
||||
authorId: author.id,
|
||||
channelId: videoChannel.id,
|
||||
duration: videoToCreateData.duration,
|
||||
createdAt: videoToCreateData.createdAt,
|
||||
// FIXME: updatedAt does not seems to be considered by Sequelize
|
||||
|
@ -336,7 +342,7 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from
|
|||
logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid)
|
||||
return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t)
|
||||
.then(videoInstance => {
|
||||
const tags = videoAttributesToUpdate.tags
|
||||
|
||||
|
@ -365,7 +371,7 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from
|
|||
|
||||
// Remove old video files
|
||||
videoInstance.VideoFiles.forEach(videoFile => {
|
||||
tasks.push(videoFile.destroy())
|
||||
tasks.push(videoFile.destroy({ transaction: t }))
|
||||
})
|
||||
|
||||
return Promise.all(tasks).then(() => ({ tagInstances, videoInstance }))
|
||||
|
@ -404,37 +410,231 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from
|
|||
})
|
||||
}
|
||||
|
||||
function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
|
||||
const options = {
|
||||
arguments: [ videoToRemoveData, fromPod ],
|
||||
errorMessage: 'Cannot remove the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(removeRemoteVideo, options)
|
||||
}
|
||||
|
||||
function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
|
||||
// We need the instance because we have to remove some other stuffs (thumbnail etc)
|
||||
return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid)
|
||||
.then(video => {
|
||||
logger.debug('Removing remote video with uuid %s.', video.uuid)
|
||||
return video.destroy()
|
||||
})
|
||||
logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
// We need the instance because we have to remove some other stuffs (thumbnail etc)
|
||||
return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
|
||||
.then(video => video.destroy({ transaction: t }))
|
||||
})
|
||||
.then(() => logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid))
|
||||
.catch(err => {
|
||||
logger.debug('Cannot remove the remote video.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
|
||||
const options = {
|
||||
arguments: [ authorToCreateData, fromPod ],
|
||||
errorMessage: 'Cannot insert the remote video author with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(addRemoteVideoAuthor, options)
|
||||
}
|
||||
|
||||
function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
|
||||
logger.debug('Adding remote video author "%s".', authorToCreateData.uuid)
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
|
||||
.then(author => {
|
||||
if (author) throw new Error('UUID already exists.')
|
||||
|
||||
return undefined
|
||||
})
|
||||
.then(() => {
|
||||
const videoAuthorData = {
|
||||
name: authorToCreateData.name,
|
||||
uuid: authorToCreateData.uuid,
|
||||
userId: null, // Not on our pod
|
||||
podId: fromPod.id
|
||||
}
|
||||
|
||||
const author = db.Author.build(videoAuthorData)
|
||||
return author.save({ transaction: t })
|
||||
})
|
||||
})
|
||||
.then(() => logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid))
|
||||
.catch(err => {
|
||||
logger.debug('Could not fetch remote video.', { host: fromPod.host, uuid: videoToRemoveData.uuid, error: err.stack })
|
||||
logger.debug('Cannot insert the remote video author.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
|
||||
const options = {
|
||||
arguments: [ authorAttributesToRemove, fromPod ],
|
||||
errorMessage: 'Cannot remove the remote video author with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(removeRemoteVideoAuthor, options)
|
||||
}
|
||||
|
||||
function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
|
||||
logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
|
||||
.then(videoAuthor => videoAuthor.destroy({ transaction: t }))
|
||||
})
|
||||
.then(() => logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid))
|
||||
.catch(err => {
|
||||
logger.debug('Cannot remove the remote video author.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
|
||||
const options = {
|
||||
arguments: [ videoChannelToCreateData, fromPod ],
|
||||
errorMessage: 'Cannot insert the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(addRemoteVideoChannel, options)
|
||||
}
|
||||
|
||||
function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
|
||||
logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
|
||||
.then(videoChannel => {
|
||||
if (videoChannel) throw new Error('UUID already exists.')
|
||||
|
||||
return undefined
|
||||
})
|
||||
.then(() => {
|
||||
const authorUUID = videoChannelToCreateData.ownerUUID
|
||||
const podId = fromPod.id
|
||||
|
||||
return db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
|
||||
})
|
||||
.then(author => {
|
||||
if (!author) throw new Error('Unknown author UUID.')
|
||||
|
||||
const videoChannelData = {
|
||||
name: videoChannelToCreateData.name,
|
||||
description: videoChannelToCreateData.description,
|
||||
uuid: videoChannelToCreateData.uuid,
|
||||
createdAt: videoChannelToCreateData.createdAt,
|
||||
updatedAt: videoChannelToCreateData.updatedAt,
|
||||
remote: true,
|
||||
authorId: author.id
|
||||
}
|
||||
|
||||
const videoChannel = db.VideoChannel.build(videoChannelData)
|
||||
return videoChannel.save({ transaction: t })
|
||||
})
|
||||
})
|
||||
.then(() => logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid))
|
||||
.catch(err => {
|
||||
logger.debug('Cannot insert the remote video channel.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
|
||||
const options = {
|
||||
arguments: [ videoChannelAttributesToUpdate, fromPod ],
|
||||
errorMessage: 'Cannot update the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(updateRemoteVideoChannel, options)
|
||||
}
|
||||
|
||||
function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
|
||||
logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid)
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
|
||||
.then(videoChannelInstance => {
|
||||
const options = { transaction: t }
|
||||
|
||||
videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
|
||||
videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
|
||||
videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
|
||||
videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
|
||||
|
||||
return videoChannelInstance.save(options)
|
||||
})
|
||||
})
|
||||
.then(() => logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid))
|
||||
.catch(err => {
|
||||
// This is just a debug because we will retry the insert
|
||||
logger.debug('Cannot update the remote video channel.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
|
||||
const options = {
|
||||
arguments: [ videoChannelAttributesToRemove, fromPod ],
|
||||
errorMessage: 'Cannot remove the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(removeRemoteVideoChannel, options)
|
||||
}
|
||||
|
||||
function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
|
||||
logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
|
||||
.then(videoChannel => videoChannel.destroy({ transaction: t }))
|
||||
})
|
||||
.then(() => logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid))
|
||||
.catch(err => {
|
||||
logger.debug('Cannot remove the remote video channel.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
|
||||
const options = {
|
||||
arguments: [ reportData, fromPod ],
|
||||
errorMessage: 'Cannot create remote abuse video with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(reportAbuseRemoteVideo, options)
|
||||
}
|
||||
|
||||
function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
|
||||
return fetchVideoByUUID(reportData.videoUUID)
|
||||
.then(video => {
|
||||
logger.debug('Reporting remote abuse for video %s.', video.id)
|
||||
logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
|
||||
|
||||
const videoAbuseData = {
|
||||
reporterUsername: reportData.reporterUsername,
|
||||
reason: reportData.reportReason,
|
||||
reporterPodId: fromPod.id,
|
||||
videoId: video.id
|
||||
}
|
||||
return db.sequelize.transaction(t => {
|
||||
return fetchVideoByUUID(reportData.videoUUID, t)
|
||||
.then(video => {
|
||||
const videoAbuseData = {
|
||||
reporterUsername: reportData.reporterUsername,
|
||||
reason: reportData.reportReason,
|
||||
reporterPodId: fromPod.id,
|
||||
videoId: video.id
|
||||
}
|
||||
|
||||
return db.VideoAbuse.create(videoAbuseData)
|
||||
})
|
||||
.catch(err => logger.error('Cannot create remote abuse video.', err))
|
||||
return db.VideoAbuse.create(videoAbuseData)
|
||||
})
|
||||
})
|
||||
.then(() => logger.info('Remote abuse for video uuid %s created', reportData.videoUUID))
|
||||
.catch(err => {
|
||||
// This is just a debug because we will retry the insert
|
||||
logger.debug('Cannot create remote abuse video', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function fetchVideoByUUID (id: string) {
|
||||
return db.Video.loadByUUID(id)
|
||||
function fetchVideoByUUID (id: string, t: Sequelize.Transaction) {
|
||||
return db.Video.loadByUUID(id, t)
|
||||
.then(video => {
|
||||
if (!video) throw new Error('Video not found')
|
||||
|
||||
|
@ -446,8 +646,8 @@ function fetchVideoByUUID (id: string) {
|
|||
})
|
||||
}
|
||||
|
||||
function fetchVideoByHostAndUUID (podHost: string, uuid: string) {
|
||||
return db.Video.loadByHostAndUUID(podHost, uuid)
|
||||
function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
|
||||
return db.Video.loadByHostAndUUID(podHost, uuid, t)
|
||||
.then(video => {
|
||||
if (!video) throw new Error('Video not found')
|
||||
|
||||
|
@ -458,3 +658,16 @@ function fetchVideoByHostAndUUID (podHost: string, uuid: string) {
|
|||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function fetchVideoChannelByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
|
||||
return db.VideoChannel.loadByHostAndUUID(podHost, uuid, t)
|
||||
.then(videoChannel => {
|
||||
if (!videoChannel) throw new Error('Video channel not found')
|
||||
|
||||
return videoChannel
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Cannot load video channel from host and uuid.', { error: err.stack, podHost, uuid })
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
|||
|
||||
import { database as db } from '../../initializers/database'
|
||||
import { USER_ROLES, CONFIG } from '../../initializers'
|
||||
import { logger, getFormattedObjects } from '../../helpers'
|
||||
import { logger, getFormattedObjects, retryTransactionWrapper } from '../../helpers'
|
||||
import {
|
||||
authenticate,
|
||||
ensureIsAdmin,
|
||||
|
@ -26,6 +26,7 @@ import {
|
|||
UserUpdate,
|
||||
UserUpdateMe
|
||||
} from '../../../shared'
|
||||
import { createUserAuthorAndChannel } from '../../lib'
|
||||
import { UserInstance } from '../../models'
|
||||
|
||||
const usersRouter = express.Router()
|
||||
|
@ -58,7 +59,7 @@ usersRouter.post('/',
|
|||
authenticate,
|
||||
ensureIsAdmin,
|
||||
usersAddValidator,
|
||||
createUser
|
||||
createUserRetryWrapper
|
||||
)
|
||||
|
||||
usersRouter.post('/register',
|
||||
|
@ -98,9 +99,22 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const options = {
|
||||
arguments: [ req, res ],
|
||||
errorMessage: 'Cannot insert the user with many retries.'
|
||||
}
|
||||
|
||||
retryTransactionWrapper(createUser, options)
|
||||
.then(() => {
|
||||
// TODO : include Location of the new user -> 201
|
||||
res.type('json').status(204).end()
|
||||
})
|
||||
.catch(err => next(err))
|
||||
}
|
||||
|
||||
function createUser (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const body: UserCreate = req.body
|
||||
|
||||
const user = db.User.build({
|
||||
username: body.username,
|
||||
password: body.password,
|
||||
|
@ -110,9 +124,12 @@ function createUser (req: express.Request, res: express.Response, next: express.
|
|||
videoQuota: body.videoQuota
|
||||
})
|
||||
|
||||
user.save()
|
||||
.then(() => res.type('json').status(204).end())
|
||||
.catch(err => next(err))
|
||||
return createUserAuthorAndChannel(user)
|
||||
.then(() => logger.info('User %s with its channel and author created.', body.username))
|
||||
.catch((err: Error) => {
|
||||
logger.debug('Cannot insert the user.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
|
@ -127,13 +144,13 @@ function registerUser (req: express.Request, res: express.Response, next: expres
|
|||
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
||||
})
|
||||
|
||||
user.save()
|
||||
return createUserAuthorAndChannel(user)
|
||||
.then(() => res.type('json').status(204).end())
|
||||
.catch(err => next(err))
|
||||
}
|
||||
|
||||
function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
db.User.loadByUsername(res.locals.oauth.token.user.username)
|
||||
db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
|
||||
.then(user => res.json(user.toFormattedJSON()))
|
||||
.catch(err => next(err))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
import * as express from 'express'
|
||||
|
||||
import { database as db } from '../../../initializers'
|
||||
import {
|
||||
logger,
|
||||
getFormattedObjects,
|
||||
retryTransactionWrapper
|
||||
} from '../../../helpers'
|
||||
import {
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
videoChannelsSortValidator,
|
||||
videoChannelsAddValidator,
|
||||
setVideoChannelsSort,
|
||||
setPagination,
|
||||
videoChannelsRemoveValidator,
|
||||
videoChannelGetValidator,
|
||||
videoChannelsUpdateValidator,
|
||||
listVideoAuthorChannelsValidator
|
||||
} from '../../../middlewares'
|
||||
import {
|
||||
createVideoChannel,
|
||||
updateVideoChannelToFriends
|
||||
} from '../../../lib'
|
||||
import { VideoChannelInstance, AuthorInstance } from '../../../models'
|
||||
import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
|
||||
|
||||
const videoChannelRouter = express.Router()
|
||||
|
||||
videoChannelRouter.get('/channels',
|
||||
paginationValidator,
|
||||
videoChannelsSortValidator,
|
||||
setVideoChannelsSort,
|
||||
setPagination,
|
||||
listVideoChannels
|
||||
)
|
||||
|
||||
videoChannelRouter.get('/authors/:authorId/channels',
|
||||
listVideoAuthorChannelsValidator,
|
||||
listVideoAuthorChannels
|
||||
)
|
||||
|
||||
videoChannelRouter.post('/channels',
|
||||
authenticate,
|
||||
videoChannelsAddValidator,
|
||||
addVideoChannelRetryWrapper
|
||||
)
|
||||
|
||||
videoChannelRouter.put('/channels/:id',
|
||||
authenticate,
|
||||
videoChannelsUpdateValidator,
|
||||
updateVideoChannelRetryWrapper
|
||||
)
|
||||
|
||||
videoChannelRouter.delete('/channels/:id',
|
||||
authenticate,
|
||||
videoChannelsRemoveValidator,
|
||||
removeVideoChannelRetryWrapper
|
||||
)
|
||||
|
||||
videoChannelRouter.get('/channels/:id',
|
||||
videoChannelGetValidator,
|
||||
getVideoChannel
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
videoChannelRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort)
|
||||
.then(result => res.json(getFormattedObjects(result.data, result.total)))
|
||||
.catch(err => next(err))
|
||||
}
|
||||
|
||||
function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
db.VideoChannel.listByAuthor(res.locals.author.id)
|
||||
.then(result => res.json(getFormattedObjects(result.data, result.total)))
|
||||
.catch(err => next(err))
|
||||
}
|
||||
|
||||
// Wrapper to video channel add that retry the function if there is a database error
|
||||
// We need this because we run the transaction in SERIALIZABLE isolation that can fail
|
||||
function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const options = {
|
||||
arguments: [ req, res ],
|
||||
errorMessage: 'Cannot insert the video video channel with many retries.'
|
||||
}
|
||||
|
||||
retryTransactionWrapper(addVideoChannel, options)
|
||||
.then(() => {
|
||||
// TODO : include Location of the new video channel -> 201
|
||||
res.type('json').status(204).end()
|
||||
})
|
||||
.catch(err => next(err))
|
||||
}
|
||||
|
||||
function addVideoChannel (req: express.Request, res: express.Response) {
|
||||
const videoChannelInfo: VideoChannelCreate = req.body
|
||||
const author: AuthorInstance = res.locals.oauth.token.User.Author
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return createVideoChannel(videoChannelInfo, author, t)
|
||||
})
|
||||
.then(videoChannelUUID => logger.info('Video channel with uuid %s created.', videoChannelUUID))
|
||||
.catch((err: Error) => {
|
||||
logger.debug('Cannot insert the video channel.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const options = {
|
||||
arguments: [ req, res ],
|
||||
errorMessage: 'Cannot update the video with many retries.'
|
||||
}
|
||||
|
||||
retryTransactionWrapper(updateVideoChannel, options)
|
||||
.then(() => res.type('json').status(204).end())
|
||||
.catch(err => next(err))
|
||||
}
|
||||
|
||||
function updateVideoChannel (req: express.Request, res: express.Response) {
|
||||
const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
|
||||
const videoChannelFieldsSave = videoChannelInstance.toJSON()
|
||||
const videoChannelInfoToUpdate: VideoChannelUpdate = req.body
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
const options = {
|
||||
transaction: t
|
||||
}
|
||||
|
||||
if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
|
||||
if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
|
||||
|
||||
return videoChannelInstance.save(options)
|
||||
.then(() => {
|
||||
const json = videoChannelInstance.toUpdateRemoteJSON()
|
||||
|
||||
// Now we'll update the video channel's meta data to our friends
|
||||
return updateVideoChannelToFriends(json, t)
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.debug('Cannot update the video channel.', err)
|
||||
|
||||
// Force fields we want to update
|
||||
// If the transaction is retried, sequelize will think the object has not changed
|
||||
// So it will skip the SQL request, even if the last one was ROLLBACKed!
|
||||
Object.keys(videoChannelFieldsSave).forEach(key => {
|
||||
const value = videoChannelFieldsSave[key]
|
||||
videoChannelInstance.set(key, value)
|
||||
})
|
||||
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const options = {
|
||||
arguments: [ req, res ],
|
||||
errorMessage: 'Cannot remove the video channel with many retries.'
|
||||
}
|
||||
|
||||
retryTransactionWrapper(removeVideoChannel, options)
|
||||
.then(() => res.type('json').status(204).end())
|
||||
.catch(err => next(err))
|
||||
}
|
||||
|
||||
function removeVideoChannel (req: express.Request, res: express.Response) {
|
||||
const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
return videoChannelInstance.destroy({ transaction: t })
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Errors when removed the video channel.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id)
|
||||
.then(videoChannelWithVideos => res.json(videoChannelWithVideos.toFormattedJSON()))
|
||||
.catch(err => next(err))
|
||||
}
|
|
@ -46,6 +46,7 @@ import { VideoCreate, VideoUpdate } from '../../../../shared'
|
|||
import { abuseVideoRouter } from './abuse'
|
||||
import { blacklistRouter } from './blacklist'
|
||||
import { rateVideoRouter } from './rate'
|
||||
import { videoChannelRouter } from './channel'
|
||||
|
||||
const videosRouter = express.Router()
|
||||
|
||||
|
@ -76,6 +77,7 @@ const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCo
|
|||
videosRouter.use('/', abuseVideoRouter)
|
||||
videosRouter.use('/', blacklistRouter)
|
||||
videosRouter.use('/', rateVideoRouter)
|
||||
videosRouter.use('/', videoChannelRouter)
|
||||
|
||||
videosRouter.get('/categories', listVideoCategories)
|
||||
videosRouter.get('/licences', listVideoLicences)
|
||||
|
@ -161,21 +163,13 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
|
|||
let videoUUID = ''
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
const user = res.locals.oauth.token.User
|
||||
let p: Promise<TagInstance[]>
|
||||
|
||||
const name = user.username
|
||||
// null because it is OUR pod
|
||||
const podId = null
|
||||
const userId = user.id
|
||||
if (!videoInfo.tags) p = Promise.resolve(undefined)
|
||||
else p = db.Tag.findOrCreateTags(videoInfo.tags, t)
|
||||
|
||||
return db.Author.findOrCreateAuthor(name, podId, userId, t)
|
||||
.then(author => {
|
||||
const tags = videoInfo.tags
|
||||
if (!tags) return { author, tagInstances: undefined }
|
||||
|
||||
return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
|
||||
})
|
||||
.then(({ author, tagInstances }) => {
|
||||
return p
|
||||
.then(tagInstances => {
|
||||
const videoData = {
|
||||
name: videoInfo.name,
|
||||
remote: false,
|
||||
|
@ -186,18 +180,18 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
|
|||
nsfw: videoInfo.nsfw,
|
||||
description: videoInfo.description,
|
||||
duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
|
||||
authorId: author.id
|
||||
channelId: res.locals.videoChannel.id
|
||||
}
|
||||
|
||||
const video = db.Video.build(videoData)
|
||||
return { author, tagInstances, video }
|
||||
return { tagInstances, video }
|
||||
})
|
||||
.then(({ author, tagInstances, video }) => {
|
||||
.then(({ tagInstances, video }) => {
|
||||
const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
|
||||
return getVideoFileHeight(videoFilePath)
|
||||
.then(height => ({ author, tagInstances, video, videoFileHeight: height }))
|
||||
.then(height => ({ tagInstances, video, videoFileHeight: height }))
|
||||
})
|
||||
.then(({ author, tagInstances, video, videoFileHeight }) => {
|
||||
.then(({ tagInstances, video, videoFileHeight }) => {
|
||||
const videoFileData = {
|
||||
extname: extname(videoPhysicalFile.filename),
|
||||
resolution: videoFileHeight,
|
||||
|
@ -205,9 +199,9 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
|
|||
}
|
||||
|
||||
const videoFile = db.VideoFile.build(videoFileData)
|
||||
return { author, tagInstances, video, videoFile }
|
||||
return { tagInstances, video, videoFile }
|
||||
})
|
||||
.then(({ author, tagInstances, video, videoFile }) => {
|
||||
.then(({ tagInstances, video, videoFile }) => {
|
||||
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
|
||||
const source = join(videoDir, videoPhysicalFile.filename)
|
||||
const destination = join(videoDir, video.getVideoFilename(videoFile))
|
||||
|
@ -216,10 +210,10 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
|
|||
.then(() => {
|
||||
// This is important in case if there is another attempt in the retry process
|
||||
videoPhysicalFile.filename = video.getVideoFilename(videoFile)
|
||||
return { author, tagInstances, video, videoFile }
|
||||
return { tagInstances, video, videoFile }
|
||||
})
|
||||
})
|
||||
.then(({ author, tagInstances, video, videoFile }) => {
|
||||
.then(({ tagInstances, video, videoFile }) => {
|
||||
const tasks = []
|
||||
|
||||
tasks.push(
|
||||
|
@ -239,15 +233,15 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
|
|||
)
|
||||
}
|
||||
|
||||
return Promise.all(tasks).then(() => ({ author, tagInstances, video, videoFile }))
|
||||
return Promise.all(tasks).then(() => ({ tagInstances, video, videoFile }))
|
||||
})
|
||||
.then(({ author, tagInstances, video, videoFile }) => {
|
||||
.then(({ tagInstances, video, videoFile }) => {
|
||||
const options = { transaction: t }
|
||||
|
||||
return video.save(options)
|
||||
.then(videoCreated => {
|
||||
// Do not forget to add Author information to the created video
|
||||
videoCreated.Author = author
|
||||
// Do not forget to add video channel information to the created video
|
||||
videoCreated.VideoChannel = res.locals.videoChannel
|
||||
videoUUID = videoCreated.uuid
|
||||
|
||||
return { tagInstances, video: videoCreated, videoFile }
|
||||
|
@ -392,7 +386,7 @@ function getVideo (req: express.Request, res: express.Response) {
|
|||
}
|
||||
|
||||
// Do not wait the view system
|
||||
res.json(videoInstance.toFormattedJSON())
|
||||
res.json(videoInstance.toFormattedDetailsJSON())
|
||||
}
|
||||
|
||||
function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
|
|
|
@ -47,7 +47,7 @@ function generateOEmbed (req: express.Request, res: express.Response, next: expr
|
|||
width: embedWidth,
|
||||
height: embedHeight,
|
||||
title: video.name,
|
||||
author_name: video.Author.name,
|
||||
author_name: video.VideoChannel.Author.name,
|
||||
provider_name: 'PeerTube',
|
||||
provider_url: webserverUrl
|
||||
}
|
||||
|
|
|
@ -3,4 +3,6 @@ export * from './misc'
|
|||
export * from './pods'
|
||||
export * from './pods'
|
||||
export * from './users'
|
||||
export * from './video-authors'
|
||||
export * from './video-channels'
|
||||
export * from './videos'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'express-validator'
|
||||
import * as validator from 'validator'
|
||||
|
||||
function exists (value: any) {
|
||||
return value !== undefined && value !== null
|
||||
|
@ -8,9 +8,29 @@ function isArray (value: any) {
|
|||
return Array.isArray(value)
|
||||
}
|
||||
|
||||
function isDateValid (value: string) {
|
||||
return exists(value) && validator.isISO8601(value)
|
||||
}
|
||||
|
||||
function isIdValid (value: string) {
|
||||
return exists(value) && validator.isInt('' + value)
|
||||
}
|
||||
|
||||
function isUUIDValid (value: string) {
|
||||
return exists(value) && validator.isUUID('' + value, 4)
|
||||
}
|
||||
|
||||
function isIdOrUUIDValid (value: string) {
|
||||
return isIdValid(value) || isUUIDValid(value)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
exists,
|
||||
isArray
|
||||
isArray,
|
||||
isIdValid,
|
||||
isUUIDValid,
|
||||
isIdOrUUIDValid,
|
||||
isDateValid
|
||||
}
|
||||
|
|
|
@ -6,18 +6,15 @@ import {
|
|||
REQUEST_ENDPOINT_ACTIONS,
|
||||
REQUEST_VIDEO_EVENT_TYPES
|
||||
} from '../../../initializers'
|
||||
import { isArray } from '../misc'
|
||||
import { isArray, isDateValid, isUUIDValid } from '../misc'
|
||||
import {
|
||||
isVideoAuthorValid,
|
||||
isVideoThumbnailDataValid,
|
||||
isVideoUUIDValid,
|
||||
isVideoAbuseReasonValid,
|
||||
isVideoAbuseReporterUsernameValid,
|
||||
isVideoViewsValid,
|
||||
isVideoLikesValid,
|
||||
isVideoDislikesValid,
|
||||
isVideoEventCountValid,
|
||||
isVideoDateValid,
|
||||
isVideoCategoryValid,
|
||||
isVideoLicenceValid,
|
||||
isVideoLanguageValid,
|
||||
|
@ -30,9 +27,22 @@ import {
|
|||
isVideoFileExtnameValid,
|
||||
isVideoFileResolutionValid
|
||||
} from '../videos'
|
||||
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
|
||||
import { isVideoAuthorNameValid } from '../video-authors'
|
||||
|
||||
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
|
||||
|
||||
const checkers: { [ id: string ]: (obj: any) => boolean } = {}
|
||||
checkers[ENDPOINT_ACTIONS.ADD_VIDEO] = checkAddVideo
|
||||
checkers[ENDPOINT_ACTIONS.UPDATE_VIDEO] = checkUpdateVideo
|
||||
checkers[ENDPOINT_ACTIONS.REMOVE_VIDEO] = checkRemoveVideo
|
||||
checkers[ENDPOINT_ACTIONS.REPORT_ABUSE] = checkReportVideo
|
||||
checkers[ENDPOINT_ACTIONS.ADD_CHANNEL] = checkAddVideoChannel
|
||||
checkers[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = checkUpdateVideoChannel
|
||||
checkers[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = checkRemoveVideoChannel
|
||||
checkers[ENDPOINT_ACTIONS.ADD_AUTHOR] = checkAddAuthor
|
||||
checkers[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = checkRemoveAuthor
|
||||
|
||||
function isEachRemoteRequestVideosValid (requests: any[]) {
|
||||
return isArray(requests) &&
|
||||
requests.every(request => {
|
||||
|
@ -40,26 +50,11 @@ function isEachRemoteRequestVideosValid (requests: any[]) {
|
|||
|
||||
if (!video) return false
|
||||
|
||||
return (
|
||||
isRequestTypeAddValid(request.type) &&
|
||||
isCommonVideoAttributesValid(video) &&
|
||||
isVideoAuthorValid(video.author) &&
|
||||
isVideoThumbnailDataValid(video.thumbnailData)
|
||||
) ||
|
||||
(
|
||||
isRequestTypeUpdateValid(request.type) &&
|
||||
isCommonVideoAttributesValid(video)
|
||||
) ||
|
||||
(
|
||||
isRequestTypeRemoveValid(request.type) &&
|
||||
isVideoUUIDValid(video.uuid)
|
||||
) ||
|
||||
(
|
||||
isRequestTypeReportAbuseValid(request.type) &&
|
||||
isVideoUUIDValid(request.data.videoUUID) &&
|
||||
isVideoAbuseReasonValid(request.data.reportReason) &&
|
||||
isVideoAbuseReporterUsernameValid(request.data.reporterUsername)
|
||||
)
|
||||
const checker = checkers[request.type]
|
||||
// We don't know the request type
|
||||
if (checker === undefined) return false
|
||||
|
||||
return checker(video)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -71,7 +66,7 @@ function isEachRemoteRequestVideosQaduValid (requests: any[]) {
|
|||
if (!video) return false
|
||||
|
||||
return (
|
||||
isVideoUUIDValid(video.uuid) &&
|
||||
isUUIDValid(video.uuid) &&
|
||||
(has(video, 'views') === false || isVideoViewsValid(video.views)) &&
|
||||
(has(video, 'likes') === false || isVideoLikesValid(video.likes)) &&
|
||||
(has(video, 'dislikes') === false || isVideoDislikesValid(video.dislikes))
|
||||
|
@ -87,7 +82,7 @@ function isEachRemoteRequestVideosEventsValid (requests: any[]) {
|
|||
if (!eventData) return false
|
||||
|
||||
return (
|
||||
isVideoUUIDValid(eventData.uuid) &&
|
||||
isUUIDValid(eventData.uuid) &&
|
||||
values(REQUEST_VIDEO_EVENT_TYPES).indexOf(eventData.eventType) !== -1 &&
|
||||
isVideoEventCountValid(eventData.count)
|
||||
)
|
||||
|
@ -105,8 +100,8 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
function isCommonVideoAttributesValid (video: any) {
|
||||
return isVideoDateValid(video.createdAt) &&
|
||||
isVideoDateValid(video.updatedAt) &&
|
||||
return isDateValid(video.createdAt) &&
|
||||
isDateValid(video.updatedAt) &&
|
||||
isVideoCategoryValid(video.category) &&
|
||||
isVideoLicenceValid(video.licence) &&
|
||||
isVideoLanguageValid(video.language) &&
|
||||
|
@ -115,7 +110,7 @@ function isCommonVideoAttributesValid (video: any) {
|
|||
isVideoDurationValid(video.duration) &&
|
||||
isVideoNameValid(video.name) &&
|
||||
isVideoTagsValid(video.tags) &&
|
||||
isVideoUUIDValid(video.uuid) &&
|
||||
isUUIDValid(video.uuid) &&
|
||||
isVideoViewsValid(video.views) &&
|
||||
isVideoLikesValid(video.likes) &&
|
||||
isVideoDislikesValid(video.dislikes) &&
|
||||
|
@ -131,18 +126,53 @@ function isCommonVideoAttributesValid (video: any) {
|
|||
})
|
||||
}
|
||||
|
||||
function isRequestTypeAddValid (value: string) {
|
||||
return value === ENDPOINT_ACTIONS.ADD
|
||||
function checkAddVideo (video: any) {
|
||||
return isCommonVideoAttributesValid(video) &&
|
||||
isUUIDValid(video.channelUUID) &&
|
||||
isVideoThumbnailDataValid(video.thumbnailData)
|
||||
}
|
||||
|
||||
function isRequestTypeUpdateValid (value: string) {
|
||||
return value === ENDPOINT_ACTIONS.UPDATE
|
||||
function checkUpdateVideo (video: any) {
|
||||
return isCommonVideoAttributesValid(video)
|
||||
}
|
||||
|
||||
function isRequestTypeRemoveValid (value: string) {
|
||||
return value === ENDPOINT_ACTIONS.REMOVE
|
||||
function checkRemoveVideo (video: any) {
|
||||
return isUUIDValid(video.uuid)
|
||||
}
|
||||
|
||||
function isRequestTypeReportAbuseValid (value: string) {
|
||||
return value === ENDPOINT_ACTIONS.REPORT_ABUSE
|
||||
function checkReportVideo (abuse: any) {
|
||||
return isUUIDValid(abuse.videoUUID) &&
|
||||
isVideoAbuseReasonValid(abuse.reportReason) &&
|
||||
isVideoAbuseReporterUsernameValid(abuse.reporterUsername)
|
||||
}
|
||||
|
||||
function checkAddVideoChannel (videoChannel: any) {
|
||||
return isUUIDValid(videoChannel.uuid) &&
|
||||
isVideoChannelNameValid(videoChannel.name) &&
|
||||
isVideoChannelDescriptionValid(videoChannel.description) &&
|
||||
isDateValid(videoChannel.createdAt) &&
|
||||
isDateValid(videoChannel.updatedAt) &&
|
||||
isUUIDValid(videoChannel.ownerUUID)
|
||||
}
|
||||
|
||||
function checkUpdateVideoChannel (videoChannel: any) {
|
||||
return isUUIDValid(videoChannel.uuid) &&
|
||||
isVideoChannelNameValid(videoChannel.name) &&
|
||||
isVideoChannelDescriptionValid(videoChannel.description) &&
|
||||
isDateValid(videoChannel.createdAt) &&
|
||||
isDateValid(videoChannel.updatedAt) &&
|
||||
isUUIDValid(videoChannel.ownerUUID)
|
||||
}
|
||||
|
||||
function checkRemoveVideoChannel (videoChannel: any) {
|
||||
return isUUIDValid(videoChannel.uuid)
|
||||
}
|
||||
|
||||
function checkAddAuthor (author: any) {
|
||||
return isUUIDValid(author.uuid) &&
|
||||
isVideoAuthorNameValid(author.name)
|
||||
}
|
||||
|
||||
function checkRemoveAuthor (author: any) {
|
||||
return isUUIDValid(author.uuid)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import * as Promise from 'bluebird'
|
||||
import * as validator from 'validator'
|
||||
import * as express from 'express'
|
||||
import 'express-validator'
|
||||
|
||||
import { database as db } from '../../initializers'
|
||||
import { AuthorInstance } from '../../models'
|
||||
import { logger } from '../logger'
|
||||
|
||||
import { isUserUsernameValid } from './users'
|
||||
|
||||
function isVideoAuthorNameValid (value: string) {
|
||||
return isUserUsernameValid(value)
|
||||
}
|
||||
|
||||
function checkVideoAuthorExists (id: string, res: express.Response, callback: () => void) {
|
||||
let promise: Promise<AuthorInstance>
|
||||
if (validator.isInt(id)) {
|
||||
promise = db.Author.load(+id)
|
||||
} else { // UUID
|
||||
promise = db.Author.loadByUUID(id)
|
||||
}
|
||||
|
||||
promise.then(author => {
|
||||
if (!author) {
|
||||
return res.status(404)
|
||||
.json({ error: 'Video author not found' })
|
||||
.end()
|
||||
}
|
||||
|
||||
res.locals.author = author
|
||||
callback()
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Error in video author request validator.', err)
|
||||
return res.sendStatus(500)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
checkVideoAuthorExists,
|
||||
isVideoAuthorNameValid
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import * as Promise from 'bluebird'
|
||||
import * as validator from 'validator'
|
||||
import * as express from 'express'
|
||||
import 'express-validator'
|
||||
import 'multer'
|
||||
|
||||
import { database as db, CONSTRAINTS_FIELDS } from '../../initializers'
|
||||
import { VideoChannelInstance } from '../../models'
|
||||
import { logger } from '../logger'
|
||||
import { exists } from './misc'
|
||||
|
||||
const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS
|
||||
|
||||
function isVideoChannelDescriptionValid (value: string) {
|
||||
return value === null || validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.DESCRIPTION)
|
||||
}
|
||||
|
||||
function isVideoChannelNameValid (value: string) {
|
||||
return exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.NAME)
|
||||
}
|
||||
|
||||
function isVideoChannelUUIDValid (value: string) {
|
||||
return exists(value) && validator.isUUID('' + value, 4)
|
||||
}
|
||||
|
||||
function checkVideoChannelExists (id: string, res: express.Response, callback: () => void) {
|
||||
let promise: Promise<VideoChannelInstance>
|
||||
if (validator.isInt(id)) {
|
||||
promise = db.VideoChannel.loadAndPopulateAuthor(+id)
|
||||
} else { // UUID
|
||||
promise = db.VideoChannel.loadByUUIDAndPopulateAuthor(id)
|
||||
}
|
||||
|
||||
promise.then(videoChannel => {
|
||||
if (!videoChannel) {
|
||||
return res.status(404)
|
||||
.json({ error: 'Video channel not found' })
|
||||
.end()
|
||||
}
|
||||
|
||||
res.locals.videoChannel = videoChannel
|
||||
callback()
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Error in video channel request validator.', err)
|
||||
return res.sendStatus(500)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
isVideoChannelDescriptionValid,
|
||||
isVideoChannelNameValid,
|
||||
isVideoChannelUUIDValid,
|
||||
checkVideoChannelExists
|
||||
}
|
|
@ -23,18 +23,6 @@ const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
|
|||
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
|
||||
const VIDEO_EVENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_EVENTS
|
||||
|
||||
function isVideoIdOrUUIDValid (value: string) {
|
||||
return validator.isInt(value) || isVideoUUIDValid(value)
|
||||
}
|
||||
|
||||
function isVideoAuthorValid (value: string) {
|
||||
return isUserUsernameValid(value)
|
||||
}
|
||||
|
||||
function isVideoDateValid (value: string) {
|
||||
return exists(value) && validator.isISO8601(value)
|
||||
}
|
||||
|
||||
function isVideoCategoryValid (value: number) {
|
||||
return VIDEO_CATEGORIES[value] !== undefined
|
||||
}
|
||||
|
@ -79,10 +67,6 @@ function isVideoThumbnailDataValid (value: string) {
|
|||
return exists(value) && validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL_DATA)
|
||||
}
|
||||
|
||||
function isVideoUUIDValid (value: string) {
|
||||
return exists(value) && validator.isUUID('' + value, 4)
|
||||
}
|
||||
|
||||
function isVideoAbuseReasonValid (value: string) {
|
||||
return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
|
||||
}
|
||||
|
@ -170,9 +154,6 @@ function checkVideoExists (id: string, res: express.Response, callback: () => vo
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
isVideoIdOrUUIDValid,
|
||||
isVideoAuthorValid,
|
||||
isVideoDateValid,
|
||||
isVideoCategoryValid,
|
||||
isVideoLicenceValid,
|
||||
isVideoLanguageValid,
|
||||
|
@ -185,7 +166,6 @@ export {
|
|||
isVideoThumbnailValid,
|
||||
isVideoThumbnailDataValid,
|
||||
isVideoFileExtnameValid,
|
||||
isVideoUUIDValid,
|
||||
isVideoAbuseReasonValid,
|
||||
isVideoAbuseReporterUsernameValid,
|
||||
isVideoFile,
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
RequestEndpoint,
|
||||
RequestVideoEventType,
|
||||
RequestVideoQaduType,
|
||||
RemoteVideoRequestType,
|
||||
JobState
|
||||
} from '../../shared/models'
|
||||
|
||||
|
@ -35,6 +36,7 @@ const SORTABLE_COLUMNS = {
|
|||
PODS: [ 'id', 'host', 'score', 'createdAt' ],
|
||||
USERS: [ 'id', 'username', 'createdAt' ],
|
||||
VIDEO_ABUSES: [ 'id', 'createdAt' ],
|
||||
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
|
||||
VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ],
|
||||
BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ]
|
||||
}
|
||||
|
@ -115,6 +117,10 @@ const CONSTRAINTS_FIELDS = {
|
|||
VIDEO_ABUSES: {
|
||||
REASON: { min: 2, max: 300 } // Length
|
||||
},
|
||||
VIDEO_CHANNELS: {
|
||||
NAME: { min: 3, max: 50 }, // Length
|
||||
DESCRIPTION: { min: 3, max: 250 } // Length
|
||||
},
|
||||
VIDEOS: {
|
||||
NAME: { min: 3, max: 50 }, // Length
|
||||
DESCRIPTION: { min: 3, max: 250 }, // Length
|
||||
|
@ -232,11 +238,20 @@ const REQUEST_ENDPOINTS: { [ id: string ]: RequestEndpoint } = {
|
|||
VIDEOS: 'videos'
|
||||
}
|
||||
|
||||
const REQUEST_ENDPOINT_ACTIONS: { [ id: string ]: any } = {}
|
||||
const REQUEST_ENDPOINT_ACTIONS: {
|
||||
[ id: string ]: {
|
||||
[ id: string ]: RemoteVideoRequestType
|
||||
}
|
||||
} = {}
|
||||
REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] = {
|
||||
ADD: 'add',
|
||||
UPDATE: 'update',
|
||||
REMOVE: 'remove',
|
||||
ADD_VIDEO: 'add-video',
|
||||
UPDATE_VIDEO: 'update-video',
|
||||
REMOVE_VIDEO: 'remove-video',
|
||||
ADD_CHANNEL: 'add-channel',
|
||||
UPDATE_CHANNEL: 'update-channel',
|
||||
REMOVE_CHANNEL: 'remove-channel',
|
||||
ADD_AUTHOR: 'add-author',
|
||||
REMOVE_AUTHOR: 'remove-author',
|
||||
REPORT_ABUSE: 'report-abuse'
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { VideoTagModel } from './../models/video/video-tag-interface'
|
|||
import { BlacklistedVideoModel } from './../models/video/video-blacklist-interface'
|
||||
import { VideoFileModel } from './../models/video/video-file-interface'
|
||||
import { VideoAbuseModel } from './../models/video/video-abuse-interface'
|
||||
import { VideoChannelModel } from './../models/video/video-channel-interface'
|
||||
import { UserModel } from './../models/user/user-interface'
|
||||
import { UserVideoRateModel } from './../models/user/user-video-rate-interface'
|
||||
import { TagModel } from './../models/video/tag-interface'
|
||||
|
@ -50,6 +51,7 @@ const database: {
|
|||
UserVideoRate?: UserVideoRateModel,
|
||||
User?: UserModel,
|
||||
VideoAbuse?: VideoAbuseModel,
|
||||
VideoChannel?: VideoChannelModel,
|
||||
VideoFile?: VideoFileModel,
|
||||
BlacklistedVideo?: BlacklistedVideoModel,
|
||||
VideoTag?: VideoTagModel,
|
||||
|
|
|
@ -5,6 +5,7 @@ import { database as db } from './database'
|
|||
import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION, CACHE } from './constants'
|
||||
import { clientsExist, usersExist } from './checker'
|
||||
import { logger, createCertsIfNotExist, mkdirpPromise, rimrafPromise } from '../helpers'
|
||||
import { createUserAuthorAndChannel } from '../lib'
|
||||
|
||||
function installApplication () {
|
||||
return db.sequelize.sync()
|
||||
|
@ -91,7 +92,7 @@ function createOAuthAdminIfNotExist () {
|
|||
const username = 'root'
|
||||
const role = USER_ROLES.ADMIN
|
||||
const email = CONFIG.ADMIN.EMAIL
|
||||
const createOptions: { validate?: boolean } = {}
|
||||
let validatePassword = true
|
||||
let password = ''
|
||||
|
||||
// Do not generate a random password for tests
|
||||
|
@ -103,7 +104,7 @@ function createOAuthAdminIfNotExist () {
|
|||
}
|
||||
|
||||
// Our password is weak so do not validate it
|
||||
createOptions.validate = false
|
||||
validatePassword = false
|
||||
} else {
|
||||
password = passwordGenerator(8, true)
|
||||
}
|
||||
|
@ -115,13 +116,15 @@ function createOAuthAdminIfNotExist () {
|
|||
role,
|
||||
videoQuota: -1
|
||||
}
|
||||
const user = db.User.build(userData)
|
||||
|
||||
return db.User.create(userData, createOptions).then(createdUser => {
|
||||
logger.info('Username: ' + username)
|
||||
logger.info('User password: ' + password)
|
||||
return createUserAuthorAndChannel(user, validatePassword)
|
||||
.then(({ user }) => {
|
||||
logger.info('Username: ' + username)
|
||||
logger.info('User password: ' + password)
|
||||
|
||||
logger.info('Creating Application table.')
|
||||
return db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION })
|
||||
})
|
||||
logger.info('Creating Application table.')
|
||||
return db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class VideosPreviewCache {
|
|||
}
|
||||
|
||||
private saveRemotePreviewAndReturnPath (video: VideoInstance) {
|
||||
const req = fetchRemotePreview(video.Author.Pod, video)
|
||||
const req = fetchRemotePreview(video)
|
||||
|
||||
return new Promise<string>((res, rej) => {
|
||||
const path = join(CACHE.DIRECTORIES.PREVIEWS, video.getPreviewName())
|
||||
|
|
|
@ -42,7 +42,13 @@ import {
|
|||
RemoteVideoRemoveData,
|
||||
RemoteVideoReportAbuseData,
|
||||
ResultList,
|
||||
Pod as FormattedPod
|
||||
RemoteVideoRequestType,
|
||||
Pod as FormattedPod,
|
||||
RemoteVideoChannelCreateData,
|
||||
RemoteVideoChannelUpdateData,
|
||||
RemoteVideoChannelRemoveData,
|
||||
RemoteVideoAuthorCreateData,
|
||||
RemoteVideoAuthorRemoveData
|
||||
} from '../../shared'
|
||||
|
||||
type QaduParam = { videoId: number, type: RequestVideoQaduType }
|
||||
|
@ -62,7 +68,7 @@ function activateSchedulers () {
|
|||
|
||||
function addVideoToFriends (videoData: RemoteVideoCreateData, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.ADD,
|
||||
type: ENDPOINT_ACTIONS.ADD_VIDEO,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: videoData,
|
||||
transaction
|
||||
|
@ -72,7 +78,7 @@ function addVideoToFriends (videoData: RemoteVideoCreateData, transaction: Seque
|
|||
|
||||
function updateVideoToFriends (videoData: RemoteVideoUpdateData, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.UPDATE,
|
||||
type: ENDPOINT_ACTIONS.UPDATE_VIDEO,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: videoData,
|
||||
transaction
|
||||
|
@ -82,7 +88,7 @@ function updateVideoToFriends (videoData: RemoteVideoUpdateData, transaction: Se
|
|||
|
||||
function removeVideoToFriends (videoParams: RemoteVideoRemoveData, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.REMOVE,
|
||||
type: ENDPOINT_ACTIONS.REMOVE_VIDEO,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: videoParams,
|
||||
transaction
|
||||
|
@ -90,12 +96,62 @@ function removeVideoToFriends (videoParams: RemoteVideoRemoveData, transaction:
|
|||
return createRequest(options)
|
||||
}
|
||||
|
||||
function addVideoAuthorToFriends (authorData: RemoteVideoAuthorCreateData, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.ADD_AUTHOR,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: authorData,
|
||||
transaction
|
||||
}
|
||||
return createRequest(options)
|
||||
}
|
||||
|
||||
function removeVideoAuthorToFriends (authorData: RemoteVideoAuthorRemoveData, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.REMOVE_AUTHOR,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: authorData,
|
||||
transaction
|
||||
}
|
||||
return createRequest(options)
|
||||
}
|
||||
|
||||
function addVideoChannelToFriends (videoChannelData: RemoteVideoChannelCreateData, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.ADD_CHANNEL,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: videoChannelData,
|
||||
transaction
|
||||
}
|
||||
return createRequest(options)
|
||||
}
|
||||
|
||||
function updateVideoChannelToFriends (videoChannelData: RemoteVideoChannelUpdateData, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.UPDATE_CHANNEL,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: videoChannelData,
|
||||
transaction
|
||||
}
|
||||
return createRequest(options)
|
||||
}
|
||||
|
||||
function removeVideoChannelToFriends (videoChannelParams: RemoteVideoChannelRemoveData, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.REMOVE_CHANNEL,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: videoChannelParams,
|
||||
transaction
|
||||
}
|
||||
return createRequest(options)
|
||||
}
|
||||
|
||||
function reportAbuseVideoToFriend (reportData: RemoteVideoReportAbuseData, video: VideoInstance, transaction: Sequelize.Transaction) {
|
||||
const options = {
|
||||
type: ENDPOINT_ACTIONS.REPORT_ABUSE,
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: reportData,
|
||||
toIds: [ video.Author.podId ],
|
||||
toIds: [ video.VideoChannel.Author.podId ],
|
||||
transaction
|
||||
}
|
||||
return createRequest(options)
|
||||
|
@ -207,15 +263,66 @@ function quitFriends () {
|
|||
.finally(() => requestScheduler.activate())
|
||||
}
|
||||
|
||||
function sendOwnedDataToPod (podId: number) {
|
||||
// First send authors
|
||||
return sendOwnedAuthorsToPod(podId)
|
||||
.then(() => sendOwnedChannelsToPod(podId))
|
||||
.then(() => sendOwnedVideosToPod(podId))
|
||||
}
|
||||
|
||||
function sendOwnedChannelsToPod (podId: number) {
|
||||
return db.VideoChannel.listOwned()
|
||||
.then(videoChannels => {
|
||||
const tasks = []
|
||||
videoChannels.forEach(videoChannel => {
|
||||
const remoteVideoChannel = videoChannel.toAddRemoteJSON()
|
||||
const options = {
|
||||
type: 'add-channel' as 'add-channel',
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: remoteVideoChannel,
|
||||
toIds: [ podId ],
|
||||
transaction: null
|
||||
}
|
||||
|
||||
const p = createRequest(options)
|
||||
tasks.push(p)
|
||||
})
|
||||
|
||||
return Promise.all(tasks)
|
||||
})
|
||||
}
|
||||
|
||||
function sendOwnedAuthorsToPod (podId: number) {
|
||||
return db.Author.listOwned()
|
||||
.then(authors => {
|
||||
const tasks = []
|
||||
authors.forEach(author => {
|
||||
const remoteAuthor = author.toAddRemoteJSON()
|
||||
const options = {
|
||||
type: 'add-author' as 'add-author',
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: remoteAuthor,
|
||||
toIds: [ podId ],
|
||||
transaction: null
|
||||
}
|
||||
|
||||
const p = createRequest(options)
|
||||
tasks.push(p)
|
||||
})
|
||||
|
||||
return Promise.all(tasks)
|
||||
})
|
||||
}
|
||||
|
||||
function sendOwnedVideosToPod (podId: number) {
|
||||
db.Video.listOwnedAndPopulateAuthorAndTags()
|
||||
return db.Video.listOwnedAndPopulateAuthorAndTags()
|
||||
.then(videosList => {
|
||||
const tasks = []
|
||||
videosList.forEach(video => {
|
||||
const promise = video.toAddRemoteJSON()
|
||||
.then(remoteVideo => {
|
||||
const options = {
|
||||
type: 'add',
|
||||
type: 'add-video' as 'add-video',
|
||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
||||
data: remoteVideo,
|
||||
toIds: [ podId ],
|
||||
|
@ -236,8 +343,8 @@ function sendOwnedVideosToPod (podId: number) {
|
|||
})
|
||||
}
|
||||
|
||||
function fetchRemotePreview (pod: PodInstance, video: VideoInstance) {
|
||||
const host = video.Author.Pod.host
|
||||
function fetchRemotePreview (video: VideoInstance) {
|
||||
const host = video.VideoChannel.Author.Pod.host
|
||||
const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName())
|
||||
|
||||
return request.get(REMOTE_SCHEME.HTTP + '://' + host + path)
|
||||
|
@ -274,7 +381,9 @@ function getRequestVideoEventScheduler () {
|
|||
export {
|
||||
activateSchedulers,
|
||||
addVideoToFriends,
|
||||
removeVideoAuthorToFriends,
|
||||
updateVideoToFriends,
|
||||
addVideoAuthorToFriends,
|
||||
reportAbuseVideoToFriend,
|
||||
quickAndDirtyUpdateVideoToFriends,
|
||||
quickAndDirtyUpdatesVideoToFriends,
|
||||
|
@ -285,11 +394,14 @@ export {
|
|||
quitFriends,
|
||||
removeFriend,
|
||||
removeVideoToFriends,
|
||||
sendOwnedVideosToPod,
|
||||
sendOwnedDataToPod,
|
||||
getRequestScheduler,
|
||||
getRequestVideoQaduScheduler,
|
||||
getRequestVideoEventScheduler,
|
||||
fetchRemotePreview
|
||||
fetchRemotePreview,
|
||||
addVideoChannelToFriends,
|
||||
updateVideoChannelToFriends,
|
||||
removeVideoChannelToFriends
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -373,7 +485,7 @@ function makeRequestsToWinningPods (cert: string, podsList: PodInstance[]) {
|
|||
.then(podCreated => {
|
||||
|
||||
// Add our videos to the request scheduler
|
||||
sendOwnedVideosToPod(podCreated.id)
|
||||
sendOwnedDataToPod(podCreated.id)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Cannot add friend %s pod.', pod.host, err)
|
||||
|
@ -397,7 +509,7 @@ function makeRequestsToWinningPods (cert: string, podsList: PodInstance[]) {
|
|||
|
||||
// Wrapper that populate "toIds" argument with all our friends if it is not specified
|
||||
type CreateRequestOptions = {
|
||||
type: string
|
||||
type: RemoteVideoRequestType
|
||||
endpoint: RequestEndpoint
|
||||
data: Object
|
||||
toIds?: number[]
|
||||
|
|
|
@ -3,3 +3,5 @@ export * from './jobs'
|
|||
export * from './request'
|
||||
export * from './friends'
|
||||
export * from './oauth-model'
|
||||
export * from './user'
|
||||
export * from './video-channel'
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { database as db } from '../initializers'
|
||||
import { UserInstance } from '../models'
|
||||
import { addVideoAuthorToFriends } from './friends'
|
||||
import { createVideoChannel } from './video-channel'
|
||||
|
||||
function createUserAuthorAndChannel (user: UserInstance, validateUser = true) {
|
||||
return db.sequelize.transaction(t => {
|
||||
const userOptions = {
|
||||
transaction: t,
|
||||
validate: validateUser
|
||||
}
|
||||
|
||||
return user.save(userOptions)
|
||||
.then(user => {
|
||||
const author = db.Author.build({
|
||||
name: user.username,
|
||||
podId: null, // It is our pod
|
||||
userId: user.id
|
||||
})
|
||||
|
||||
return author.save({ transaction: t })
|
||||
.then(author => ({ author, user }))
|
||||
})
|
||||
.then(({ author, user }) => {
|
||||
const remoteVideoAuthor = author.toAddRemoteJSON()
|
||||
|
||||
// Now we'll add the video channel's meta data to our friends
|
||||
return addVideoAuthorToFriends(remoteVideoAuthor, t)
|
||||
.then(() => ({ author, user }))
|
||||
})
|
||||
.then(({ author, user }) => {
|
||||
const videoChannelInfo = {
|
||||
name: `Default ${user.username} channel`
|
||||
}
|
||||
|
||||
return createVideoChannel(videoChannelInfo, author, t)
|
||||
.then(videoChannel => ({ author, user, videoChannel }))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
createUserAuthorAndChannel
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
import { addVideoChannelToFriends } from './friends'
|
||||
import { database as db } from '../initializers'
|
||||
import { AuthorInstance } from '../models'
|
||||
import { VideoChannelCreate } from '../../shared/models'
|
||||
|
||||
function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: AuthorInstance, t: Sequelize.Transaction) {
|
||||
let videoChannelUUID = ''
|
||||
|
||||
const videoChannelData = {
|
||||
name: videoChannelInfo.name,
|
||||
description: videoChannelInfo.description,
|
||||
remote: false,
|
||||
authorId: author.id
|
||||
}
|
||||
|
||||
const videoChannel = db.VideoChannel.build(videoChannelData)
|
||||
const options = { transaction: t }
|
||||
|
||||
return videoChannel.save(options)
|
||||
.then(videoChannelCreated => {
|
||||
// Do not forget to add Author information to the created video channel
|
||||
videoChannelCreated.Author = author
|
||||
videoChannelUUID = videoChannelCreated.uuid
|
||||
|
||||
return videoChannelCreated
|
||||
})
|
||||
.then(videoChannel => {
|
||||
const remoteVideoChannel = videoChannel.toAddRemoteJSON()
|
||||
|
||||
// Now we'll add the video channel's meta data to our friends
|
||||
return addVideoChannelToFriends(remoteVideoChannel, t)
|
||||
})
|
||||
.then(() => videoChannelUUID) // Return video channel UUID
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
createVideoChannel
|
||||
}
|
|
@ -22,6 +22,12 @@ function setVideoAbusesSort (req: express.Request, res: express.Response, next:
|
|||
return next()
|
||||
}
|
||||
|
||||
function setVideoChannelsSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
if (!req.query.sort) req.query.sort = '-createdAt'
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
function setVideosSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
if (!req.query.sort) req.query.sort = '-createdAt'
|
||||
|
||||
|
@ -55,6 +61,7 @@ export {
|
|||
setPodsSort,
|
||||
setUsersSort,
|
||||
setVideoAbusesSort,
|
||||
setVideoChannelsSort,
|
||||
setVideosSort,
|
||||
setBlacklistSort
|
||||
}
|
||||
|
|
|
@ -6,3 +6,4 @@ export * from './sort'
|
|||
export * from './users'
|
||||
export * from './videos'
|
||||
export * from './video-blacklist'
|
||||
export * from './video-channels'
|
||||
|
|
|
@ -4,9 +4,12 @@ import { join } from 'path'
|
|||
|
||||
import { checkErrors } from './utils'
|
||||
import { CONFIG } from '../../initializers'
|
||||
import { logger } from '../../helpers'
|
||||
import { checkVideoExists, isVideoIdOrUUIDValid } from '../../helpers/custom-validators/videos'
|
||||
import { isTestInstance } from '../../helpers/core-utils'
|
||||
import {
|
||||
logger,
|
||||
isTestInstance,
|
||||
checkVideoExists,
|
||||
isIdOrUUIDValid
|
||||
} from '../../helpers'
|
||||
|
||||
const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/'
|
||||
const videoWatchRegex = new RegExp('([^/]+)$')
|
||||
|
@ -45,7 +48,7 @@ const oembedValidator = [
|
|||
}
|
||||
|
||||
const videoId = matches[1]
|
||||
if (isVideoIdOrUUIDValid(videoId) === false) {
|
||||
if (isIdOrUUIDValid(videoId) === false) {
|
||||
return res.status(400)
|
||||
.json({ error: 'Invalid video id.' })
|
||||
.end()
|
||||
|
|
|
@ -11,12 +11,14 @@ const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS)
|
|||
const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
|
||||
const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
|
||||
const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
|
||||
const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS)
|
||||
|
||||
const podsSortValidator = checkSort(SORTABLE_PODS_COLUMNS)
|
||||
const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
|
||||
const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
|
||||
const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
|
||||
const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
|
||||
const videoChannelsSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_COLUMNS)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -24,6 +26,7 @@ export {
|
|||
podsSortValidator,
|
||||
usersSortValidator,
|
||||
videoAbusesSortValidator,
|
||||
videoChannelsSortValidator,
|
||||
videosSortValidator,
|
||||
blacklistSortValidator
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
isUserPasswordValid,
|
||||
isUserVideoQuotaValid,
|
||||
isUserDisplayNSFWValid,
|
||||
isVideoIdOrUUIDValid
|
||||
isIdOrUUIDValid
|
||||
} from '../../helpers'
|
||||
import { UserInstance, VideoInstance } from '../../models'
|
||||
|
||||
|
@ -109,7 +109,7 @@ const usersGetValidator = [
|
|||
]
|
||||
|
||||
const usersVideoRatingValidator = [
|
||||
param('videoId').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
|
||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
|
||||
|
|
|
@ -3,10 +3,10 @@ import * as express from 'express'
|
|||
|
||||
import { database as db } from '../../initializers/database'
|
||||
import { checkErrors } from './utils'
|
||||
import { logger, isVideoIdOrUUIDValid, checkVideoExists } from '../../helpers'
|
||||
import { logger, isIdOrUUIDValid, checkVideoExists } from '../../helpers'
|
||||
|
||||
const videosBlacklistRemoveValidator = [
|
||||
param('videoId').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
|
||||
|
@ -20,7 +20,7 @@ const videosBlacklistRemoveValidator = [
|
|||
]
|
||||
|
||||
const videosBlacklistAddValidator = [
|
||||
param('videoId').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videosBlacklist parameters', { parameters: req.params })
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
import { body, param } from 'express-validator/check'
|
||||
import * as express from 'express'
|
||||
|
||||
import { checkErrors } from './utils'
|
||||
import { database as db } from '../../initializers'
|
||||
import {
|
||||
logger,
|
||||
isIdOrUUIDValid,
|
||||
isVideoChannelDescriptionValid,
|
||||
isVideoChannelNameValid,
|
||||
checkVideoChannelExists,
|
||||
checkVideoAuthorExists
|
||||
} from '../../helpers'
|
||||
|
||||
const listVideoAuthorChannelsValidator = [
|
||||
param('authorId').custom(isIdOrUUIDValid).withMessage('Should have a valid author id'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking listVideoAuthorChannelsValidator parameters', { parameters: req.body })
|
||||
|
||||
checkErrors(req, res, () => {
|
||||
checkVideoAuthorExists(req.params.authorId, res, next)
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
const videoChannelsAddValidator = [
|
||||
body('name').custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
|
||||
body('description').custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body })
|
||||
|
||||
checkErrors(req, res, next)
|
||||
}
|
||||
]
|
||||
|
||||
const videoChannelsUpdateValidator = [
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
|
||||
body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
|
||||
|
||||
checkErrors(req, res, () => {
|
||||
checkVideoChannelExists(req.params.id, res, () => {
|
||||
// We need to make additional checks
|
||||
if (res.locals.videoChannel.isOwned() === false) {
|
||||
return res.status(403)
|
||||
.json({ error: 'Cannot update video channel of another pod' })
|
||||
.end()
|
||||
}
|
||||
|
||||
if (res.locals.videoChannel.Author.userId !== res.locals.oauth.token.User.id) {
|
||||
return res.status(403)
|
||||
.json({ error: 'Cannot update video channel of another user' })
|
||||
.end()
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
const videoChannelsRemoveValidator = [
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
|
||||
|
||||
checkErrors(req, res, () => {
|
||||
checkVideoChannelExists(req.params.id, res, () => {
|
||||
// Check if the user who did the request is able to delete the video
|
||||
checkUserCanDeleteVideoChannel(res, () => {
|
||||
checkVideoChannelIsNotTheLastOne(res, next)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
const videoChannelGetValidator = [
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videoChannelsGet parameters', { parameters: req.params })
|
||||
|
||||
checkErrors(req, res, () => {
|
||||
checkVideoChannelExists(req.params.id, res, next)
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
listVideoAuthorChannelsValidator,
|
||||
videoChannelsAddValidator,
|
||||
videoChannelsUpdateValidator,
|
||||
videoChannelsRemoveValidator,
|
||||
videoChannelGetValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function checkUserCanDeleteVideoChannel (res: express.Response, callback: () => void) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
// Retrieve the user who did the request
|
||||
if (res.locals.videoChannel.isOwned() === false) {
|
||||
return res.status(403)
|
||||
.json({ error: 'Cannot remove video channel of another pod.' })
|
||||
.end()
|
||||
}
|
||||
|
||||
// Check if the user can delete the video channel
|
||||
// The user can delete it if s/he is an admin
|
||||
// Or if s/he is the video channel's author
|
||||
if (user.isAdmin() === false && res.locals.videoChannel.Author.userId !== user.id) {
|
||||
return res.status(403)
|
||||
.json({ error: 'Cannot remove video channel of another user' })
|
||||
.end()
|
||||
}
|
||||
|
||||
// If we reach this comment, we can delete the video
|
||||
callback()
|
||||
}
|
||||
|
||||
function checkVideoChannelIsNotTheLastOne (res: express.Response, callback: () => void) {
|
||||
db.VideoChannel.countByAuthor(res.locals.oauth.token.User.Author.id)
|
||||
.then(count => {
|
||||
if (count <= 1) {
|
||||
return res.status(409)
|
||||
.json({ error: 'Cannot remove the last channel of this user' })
|
||||
.end()
|
||||
}
|
||||
|
||||
callback()
|
||||
})
|
||||
}
|
|
@ -15,11 +15,12 @@ import {
|
|||
isVideoLanguageValid,
|
||||
isVideoTagsValid,
|
||||
isVideoNSFWValid,
|
||||
isVideoIdOrUUIDValid,
|
||||
isIdOrUUIDValid,
|
||||
isVideoAbuseReasonValid,
|
||||
isVideoRatingTypeValid,
|
||||
getDurationFromVideoFile,
|
||||
checkVideoExists
|
||||
checkVideoExists,
|
||||
isIdValid
|
||||
} from '../../helpers'
|
||||
|
||||
const videosAddValidator = [
|
||||
|
@ -33,6 +34,7 @@ const videosAddValidator = [
|
|||
body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
|
||||
body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'),
|
||||
body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
|
||||
body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'),
|
||||
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -42,7 +44,20 @@ const videosAddValidator = [
|
|||
const videoFile: Express.Multer.File = req.files['videofile'][0]
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
user.isAbleToUploadVideo(videoFile)
|
||||
return db.VideoChannel.loadByIdAndAuthor(req.body.channelId, user.Author.id)
|
||||
.then(videoChannel => {
|
||||
if (!videoChannel) {
|
||||
res.status(400)
|
||||
.json({ error: 'Unknown video video channel for this author.' })
|
||||
.end()
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
res.locals.videoChannel = videoChannel
|
||||
|
||||
return user.isAbleToUploadVideo(videoFile)
|
||||
})
|
||||
.then(isAble => {
|
||||
if (isAble === false) {
|
||||
res.status(403)
|
||||
|
@ -88,7 +103,7 @@ const videosAddValidator = [
|
|||
]
|
||||
|
||||
const videosUpdateValidator = [
|
||||
param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'),
|
||||
body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
|
||||
body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
|
||||
|
@ -109,7 +124,7 @@ const videosUpdateValidator = [
|
|||
.end()
|
||||
}
|
||||
|
||||
if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
|
||||
if (res.locals.video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) {
|
||||
return res.status(403)
|
||||
.json({ error: 'Cannot update video of another user' })
|
||||
.end()
|
||||
|
@ -122,7 +137,7 @@ const videosUpdateValidator = [
|
|||
]
|
||||
|
||||
const videosGetValidator = [
|
||||
param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videosGet parameters', { parameters: req.params })
|
||||
|
@ -134,7 +149,7 @@ const videosGetValidator = [
|
|||
]
|
||||
|
||||
const videosRemoveValidator = [
|
||||
param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videosRemove parameters', { parameters: req.params })
|
||||
|
@ -162,7 +177,7 @@ const videosSearchValidator = [
|
|||
]
|
||||
|
||||
const videoAbuseReportValidator = [
|
||||
param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -175,7 +190,7 @@ const videoAbuseReportValidator = [
|
|||
]
|
||||
|
||||
const videoRateValidator = [
|
||||
param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
|
|
@ -126,7 +126,17 @@ getByTokenAndPopulateUser = function (bearerToken: string) {
|
|||
where: {
|
||||
accessToken: bearerToken
|
||||
},
|
||||
include: [ OAuthToken['sequelize'].models.User ]
|
||||
include: [
|
||||
{
|
||||
model: OAuthToken['sequelize'].models.User,
|
||||
include: [
|
||||
{
|
||||
model: OAuthToken['sequelize'].models.Author,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return OAuthToken.findOne(query).then(token => {
|
||||
|
@ -141,7 +151,17 @@ getByRefreshTokenAndPopulateUser = function (refreshToken: string) {
|
|||
where: {
|
||||
refreshToken: refreshToken
|
||||
},
|
||||
include: [ OAuthToken['sequelize'].models.User ]
|
||||
include: [
|
||||
{
|
||||
model: OAuthToken['sequelize'].models.User,
|
||||
include: [
|
||||
{
|
||||
model: OAuthToken['sequelize'].models.Author,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return OAuthToken.findOne(query).then(token => {
|
||||
|
|
|
@ -85,7 +85,8 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe
|
|||
const Pod = db.Pod
|
||||
|
||||
// We make a join between videos and authors to find the podId of our video event requests
|
||||
const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' +
|
||||
const podJoins = 'INNER JOIN "VideoChannels" ON "VideoChannels"."authorId" = "Authors"."id" ' +
|
||||
'INNER JOIN "Videos" ON "Videos"."channelId" = "VideoChannels"."id" ' +
|
||||
'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"'
|
||||
|
||||
return Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins).then(podIds => {
|
||||
|
@ -161,7 +162,7 @@ function groupAndTruncateRequests (events: RequestVideoEventInstance[], limitReq
|
|||
const eventsGrouped: RequestsVideoEventGrouped = {}
|
||||
|
||||
events.forEach(event => {
|
||||
const pod = event.Video.Author.Pod
|
||||
const pod = event.Video.VideoChannel.Author.Pod
|
||||
|
||||
if (!eventsGrouped[pod.id]) eventsGrouped[pod.id] = []
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import * as Promise from 'bluebird'
|
|||
import { User as FormattedUser } from '../../../shared/models/users/user.model'
|
||||
import { UserRole } from '../../../shared/models/users/user-role.type'
|
||||
import { ResultList } from '../../../shared/models/result-list.model'
|
||||
import { AuthorInstance } from '../video/author-interface'
|
||||
|
||||
export namespace UserMethods {
|
||||
export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean>
|
||||
|
@ -17,13 +18,12 @@ export namespace UserMethods {
|
|||
|
||||
export type GetByUsername = (username: string) => Promise<UserInstance>
|
||||
|
||||
export type List = () => Promise<UserInstance[]>
|
||||
|
||||
export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<UserInstance> >
|
||||
|
||||
export type LoadById = (id: number) => Promise<UserInstance>
|
||||
|
||||
export type LoadByUsername = (username: string) => Promise<UserInstance>
|
||||
export type LoadByUsernameAndPopulateChannels = (username: string) => Promise<UserInstance>
|
||||
|
||||
export type LoadByUsernameOrEmail = (username: string, email: string) => Promise<UserInstance>
|
||||
}
|
||||
|
@ -36,10 +36,10 @@ export interface UserClass {
|
|||
|
||||
countTotal: UserMethods.CountTotal,
|
||||
getByUsername: UserMethods.GetByUsername,
|
||||
list: UserMethods.List,
|
||||
listForApi: UserMethods.ListForApi,
|
||||
loadById: UserMethods.LoadById,
|
||||
loadByUsername: UserMethods.LoadByUsername,
|
||||
loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels,
|
||||
loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,8 @@ export interface UserAttributes {
|
|||
displayNSFW?: boolean
|
||||
role: UserRole
|
||||
videoQuota: number
|
||||
|
||||
Author?: AuthorInstance
|
||||
}
|
||||
|
||||
export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> {
|
||||
|
|
|
@ -27,10 +27,10 @@ let toFormattedJSON: UserMethods.ToFormattedJSON
|
|||
let isAdmin: UserMethods.IsAdmin
|
||||
let countTotal: UserMethods.CountTotal
|
||||
let getByUsername: UserMethods.GetByUsername
|
||||
let list: UserMethods.List
|
||||
let listForApi: UserMethods.ListForApi
|
||||
let loadById: UserMethods.LoadById
|
||||
let loadByUsername: UserMethods.LoadByUsername
|
||||
let loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels
|
||||
let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
|
||||
let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo
|
||||
|
||||
|
@ -113,10 +113,10 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
|
||||
countTotal,
|
||||
getByUsername,
|
||||
list,
|
||||
listForApi,
|
||||
loadById,
|
||||
loadByUsername,
|
||||
loadByUsernameAndPopulateChannels,
|
||||
loadByUsernameOrEmail
|
||||
]
|
||||
const instanceMethods = [
|
||||
|
@ -144,15 +144,34 @@ isPasswordMatch = function (this: UserInstance, password: string) {
|
|||
}
|
||||
|
||||
toFormattedJSON = function (this: UserInstance) {
|
||||
return {
|
||||
const json = {
|
||||
id: this.id,
|
||||
username: this.username,
|
||||
email: this.email,
|
||||
displayNSFW: this.displayNSFW,
|
||||
role: this.role,
|
||||
videoQuota: this.videoQuota,
|
||||
createdAt: this.createdAt
|
||||
createdAt: this.createdAt,
|
||||
author: {
|
||||
id: this.Author.id,
|
||||
uuid: this.Author.uuid
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(this.Author.VideoChannels) === true) {
|
||||
const videoChannels = this.Author.VideoChannels
|
||||
.map(c => c.toFormattedJSON())
|
||||
.sort((v1, v2) => {
|
||||
if (v1.createdAt < v2.createdAt) return -1
|
||||
if (v1.createdAt === v2.createdAt) return 0
|
||||
|
||||
return 1
|
||||
})
|
||||
|
||||
json['videoChannels'] = videoChannels
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
isAdmin = function (this: UserInstance) {
|
||||
|
@ -189,21 +208,19 @@ getByUsername = function (username: string) {
|
|||
const query = {
|
||||
where: {
|
||||
username: username
|
||||
}
|
||||
},
|
||||
include: [ { model: User['sequelize'].models.Author, required: true } ]
|
||||
}
|
||||
|
||||
return User.findOne(query)
|
||||
}
|
||||
|
||||
list = function () {
|
||||
return User.findAll()
|
||||
}
|
||||
|
||||
listForApi = function (start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ]
|
||||
order: [ getSort(sort) ],
|
||||
include: [ { model: User['sequelize'].models.Author, required: true } ]
|
||||
}
|
||||
|
||||
return User.findAndCountAll(query).then(({ rows, count }) => {
|
||||
|
@ -215,14 +232,36 @@ listForApi = function (start: number, count: number, sort: string) {
|
|||
}
|
||||
|
||||
loadById = function (id: number) {
|
||||
return User.findById(id)
|
||||
const options = {
|
||||
include: [ { model: User['sequelize'].models.Author, required: true } ]
|
||||
}
|
||||
|
||||
return User.findById(id, options)
|
||||
}
|
||||
|
||||
loadByUsername = function (username: string) {
|
||||
const query = {
|
||||
where: {
|
||||
username
|
||||
}
|
||||
},
|
||||
include: [ { model: User['sequelize'].models.Author, required: true } ]
|
||||
}
|
||||
|
||||
return User.findOne(query)
|
||||
}
|
||||
|
||||
loadByUsernameAndPopulateChannels = function (username: string) {
|
||||
const query = {
|
||||
where: {
|
||||
username
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: User['sequelize'].models.Author,
|
||||
required: true,
|
||||
include: [ User['sequelize'].models.VideoChannel ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return User.findOne(query)
|
||||
|
@ -230,6 +269,7 @@ loadByUsername = function (username: string) {
|
|||
|
||||
loadByUsernameOrEmail = function (username: string, email: string) {
|
||||
const query = {
|
||||
include: [ { model: User['sequelize'].models.Author, required: true } ],
|
||||
where: {
|
||||
$or: [ { username }, { email } ]
|
||||
}
|
||||
|
@ -242,11 +282,12 @@ loadByUsernameOrEmail = function (username: string, email: string) {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getOriginalVideoFileTotalFromUser (user: UserInstance) {
|
||||
// Don't use sequelize because we need to use a subquery
|
||||
// Don't use sequelize because we need to use a sub query
|
||||
const query = 'SELECT SUM("size") AS "total" FROM ' +
|
||||
'(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
|
||||
'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
|
||||
'INNER JOIN "Authors" ON "Videos"."authorId" = "Authors"."id" ' +
|
||||
'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' +
|
||||
'INNER JOIN "Authors" ON "VideoChannels"."authorId" = "Authors"."id" ' +
|
||||
'INNER JOIN "Users" ON "Authors"."userId" = "Users"."id" ' +
|
||||
'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
|
||||
|
||||
|
|
|
@ -2,31 +2,44 @@ import * as Sequelize from 'sequelize'
|
|||
import * as Promise from 'bluebird'
|
||||
|
||||
import { PodInstance } from '../pod/pod-interface'
|
||||
import { RemoteVideoAuthorCreateData } from '../../../shared/models/pods/remote-video/remote-video-author-create-request.model'
|
||||
import { VideoChannelInstance } from './video-channel-interface'
|
||||
|
||||
export namespace AuthorMethods {
|
||||
export type FindOrCreateAuthor = (
|
||||
name: string,
|
||||
podId: number,
|
||||
userId: number,
|
||||
transaction: Sequelize.Transaction
|
||||
) => Promise<AuthorInstance>
|
||||
export type Load = (id: number) => Promise<AuthorInstance>
|
||||
export type LoadByUUID = (uuid: string) => Promise<AuthorInstance>
|
||||
export type LoadAuthorByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Promise<AuthorInstance>
|
||||
export type ListOwned = () => Promise<AuthorInstance[]>
|
||||
|
||||
export type ToAddRemoteJSON = (this: AuthorInstance) => RemoteVideoAuthorCreateData
|
||||
export type IsOwned = (this: AuthorInstance) => boolean
|
||||
}
|
||||
|
||||
export interface AuthorClass {
|
||||
findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor
|
||||
loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID
|
||||
load: AuthorMethods.Load
|
||||
loadByUUID: AuthorMethods.LoadByUUID
|
||||
listOwned: AuthorMethods.ListOwned
|
||||
}
|
||||
|
||||
export interface AuthorAttributes {
|
||||
name: string
|
||||
uuid?: string
|
||||
|
||||
podId?: number
|
||||
userId?: number
|
||||
}
|
||||
|
||||
export interface AuthorInstance extends AuthorClass, AuthorAttributes, Sequelize.Instance<AuthorAttributes> {
|
||||
isOwned: AuthorMethods.IsOwned
|
||||
toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON
|
||||
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
|
||||
podId: number
|
||||
Pod: PodInstance
|
||||
VideoChannels: VideoChannelInstance[]
|
||||
}
|
||||
|
||||
export interface AuthorModel extends AuthorClass, Sequelize.Model<AuthorInstance, AuthorAttributes> {}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
import { isUserUsernameValid } from '../../helpers'
|
||||
import { removeVideoAuthorToFriends } from '../../lib'
|
||||
|
||||
import { addMethodsToModel } from '../utils'
|
||||
import {
|
||||
|
@ -11,11 +12,24 @@ import {
|
|||
} from './author-interface'
|
||||
|
||||
let Author: Sequelize.Model<AuthorInstance, AuthorAttributes>
|
||||
let findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor
|
||||
let loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID
|
||||
let load: AuthorMethods.Load
|
||||
let loadByUUID: AuthorMethods.LoadByUUID
|
||||
let listOwned: AuthorMethods.ListOwned
|
||||
let isOwned: AuthorMethods.IsOwned
|
||||
let toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON
|
||||
|
||||
export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
||||
Author = sequelize.define<AuthorInstance, AuthorAttributes>('Author',
|
||||
{
|
||||
uuid: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
isUUID: 4
|
||||
}
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
|
@ -43,12 +57,23 @@ export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes:
|
|||
fields: [ 'name', 'podId' ],
|
||||
unique: true
|
||||
}
|
||||
]
|
||||
],
|
||||
hooks: { afterDestroy }
|
||||
}
|
||||
)
|
||||
|
||||
const classMethods = [ associate, findOrCreateAuthor ]
|
||||
addMethodsToModel(Author, classMethods)
|
||||
const classMethods = [
|
||||
associate,
|
||||
loadAuthorByPodAndUUID,
|
||||
load,
|
||||
loadByUUID,
|
||||
listOwned
|
||||
]
|
||||
const instanceMethods = [
|
||||
isOwned,
|
||||
toAddRemoteJSON
|
||||
]
|
||||
addMethodsToModel(Author, classMethods, instanceMethods)
|
||||
|
||||
return Author
|
||||
}
|
||||
|
@ -72,27 +97,75 @@ function associate (models) {
|
|||
onDelete: 'cascade'
|
||||
})
|
||||
|
||||
Author.hasMany(models.Video, {
|
||||
Author.hasMany(models.VideoChannel, {
|
||||
foreignKey: {
|
||||
name: 'authorId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
onDelete: 'cascade',
|
||||
hooks: true
|
||||
})
|
||||
}
|
||||
|
||||
findOrCreateAuthor = function (name: string, podId: number, userId: number, transaction: Sequelize.Transaction) {
|
||||
const author = {
|
||||
name,
|
||||
podId,
|
||||
userId
|
||||
function afterDestroy (author: AuthorInstance, options: { transaction: Sequelize.Transaction }) {
|
||||
if (author.isOwned()) {
|
||||
const removeVideoAuthorToFriendsParams = {
|
||||
uuid: author.uuid
|
||||
}
|
||||
|
||||
return removeVideoAuthorToFriends(removeVideoAuthorToFriendsParams, options.transaction)
|
||||
}
|
||||
|
||||
const query: Sequelize.FindOrInitializeOptions<AuthorAttributes> = {
|
||||
where: author,
|
||||
defaults: author,
|
||||
return undefined
|
||||
}
|
||||
|
||||
toAddRemoteJSON = function (this: AuthorInstance) {
|
||||
const json = {
|
||||
uuid: this.uuid,
|
||||
name: this.name
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
isOwned = function (this: AuthorInstance) {
|
||||
return this.podId === null
|
||||
}
|
||||
|
||||
// ------------------------------ STATICS ------------------------------
|
||||
|
||||
listOwned = function () {
|
||||
const query: Sequelize.FindOptions<AuthorAttributes> = {
|
||||
where: {
|
||||
podId: null
|
||||
}
|
||||
}
|
||||
|
||||
return Author.findAll(query)
|
||||
}
|
||||
|
||||
load = function (id: number) {
|
||||
return Author.findById(id)
|
||||
}
|
||||
|
||||
loadByUUID = function (uuid: string) {
|
||||
const query: Sequelize.FindOptions<AuthorAttributes> = {
|
||||
where: {
|
||||
uuid
|
||||
}
|
||||
}
|
||||
|
||||
return Author.findOne(query)
|
||||
}
|
||||
|
||||
loadAuthorByPodAndUUID = function (uuid: string, podId: number, transaction: Sequelize.Transaction) {
|
||||
const query: Sequelize.FindOptions<AuthorAttributes> = {
|
||||
where: {
|
||||
podId,
|
||||
uuid
|
||||
},
|
||||
transaction
|
||||
}
|
||||
|
||||
return Author.findOrCreate(query).then(([ authorInstance ]) => authorInstance)
|
||||
return Author.find(query)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ export * from './author-interface'
|
|||
export * from './tag-interface'
|
||||
export * from './video-abuse-interface'
|
||||
export * from './video-blacklist-interface'
|
||||
export * from './video-channel-interface'
|
||||
export * from './video-tag-interface'
|
||||
export * from './video-file-interface'
|
||||
export * from './video-interface'
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import * as Promise from 'bluebird'
|
||||
|
||||
import { ResultList, RemoteVideoChannelCreateData, RemoteVideoChannelUpdateData } from '../../../shared'
|
||||
|
||||
// Don't use barrel, import just what we need
|
||||
import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model'
|
||||
import { AuthorInstance } from './author-interface'
|
||||
import { VideoInstance } from './video-interface'
|
||||
|
||||
export namespace VideoChannelMethods {
|
||||
export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel
|
||||
export type ToAddRemoteJSON = (this: VideoChannelInstance) => RemoteVideoChannelCreateData
|
||||
export type ToUpdateRemoteJSON = (this: VideoChannelInstance) => RemoteVideoChannelUpdateData
|
||||
export type IsOwned = (this: VideoChannelInstance) => boolean
|
||||
|
||||
export type CountByAuthor = (authorId: number) => Promise<number>
|
||||
export type ListOwned = () => Promise<VideoChannelInstance[]>
|
||||
export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoChannelInstance> >
|
||||
export type LoadByIdAndAuthor = (id: number, authorId: number) => Promise<VideoChannelInstance>
|
||||
export type ListByAuthor = (authorId: number) => Promise< ResultList<VideoChannelInstance> >
|
||||
export type LoadAndPopulateAuthor = (id: number) => Promise<VideoChannelInstance>
|
||||
export type LoadByUUIDAndPopulateAuthor = (uuid: string) => Promise<VideoChannelInstance>
|
||||
export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
|
||||
export type LoadByHostAndUUID = (uuid: string, podHost: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
|
||||
export type LoadAndPopulateAuthorAndVideos = (id: number) => Promise<VideoChannelInstance>
|
||||
}
|
||||
|
||||
export interface VideoChannelClass {
|
||||
countByAuthor: VideoChannelMethods.CountByAuthor
|
||||
listForApi: VideoChannelMethods.ListForApi
|
||||
listByAuthor: VideoChannelMethods.ListByAuthor
|
||||
listOwned: VideoChannelMethods.ListOwned
|
||||
loadByIdAndAuthor: VideoChannelMethods.LoadByIdAndAuthor
|
||||
loadByUUID: VideoChannelMethods.LoadByUUID
|
||||
loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID
|
||||
loadAndPopulateAuthor: VideoChannelMethods.LoadAndPopulateAuthor
|
||||
loadByUUIDAndPopulateAuthor: VideoChannelMethods.LoadByUUIDAndPopulateAuthor
|
||||
loadAndPopulateAuthorAndVideos: VideoChannelMethods.LoadAndPopulateAuthorAndVideos
|
||||
}
|
||||
|
||||
export interface VideoChannelAttributes {
|
||||
id?: number
|
||||
uuid?: string
|
||||
name: string
|
||||
description: string
|
||||
remote: boolean
|
||||
|
||||
Author?: AuthorInstance
|
||||
Videos?: VideoInstance[]
|
||||
}
|
||||
|
||||
export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAttributes, Sequelize.Instance<VideoChannelAttributes> {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
|
||||
isOwned: VideoChannelMethods.IsOwned
|
||||
toFormattedJSON: VideoChannelMethods.ToFormattedJSON
|
||||
toAddRemoteJSON: VideoChannelMethods.ToAddRemoteJSON
|
||||
toUpdateRemoteJSON: VideoChannelMethods.ToUpdateRemoteJSON
|
||||
}
|
||||
|
||||
export interface VideoChannelModel extends VideoChannelClass, Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> {}
|
|
@ -0,0 +1,349 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
import { isVideoChannelNameValid, isVideoChannelDescriptionValid } from '../../helpers'
|
||||
import { removeVideoChannelToFriends } from '../../lib'
|
||||
|
||||
import { addMethodsToModel, getSort } from '../utils'
|
||||
import {
|
||||
VideoChannelInstance,
|
||||
VideoChannelAttributes,
|
||||
|
||||
VideoChannelMethods
|
||||
} from './video-channel-interface'
|
||||
|
||||
let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
|
||||
let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
|
||||
let toAddRemoteJSON: VideoChannelMethods.ToAddRemoteJSON
|
||||
let toUpdateRemoteJSON: VideoChannelMethods.ToUpdateRemoteJSON
|
||||
let isOwned: VideoChannelMethods.IsOwned
|
||||
let countByAuthor: VideoChannelMethods.CountByAuthor
|
||||
let listOwned: VideoChannelMethods.ListOwned
|
||||
let listForApi: VideoChannelMethods.ListForApi
|
||||
let listByAuthor: VideoChannelMethods.ListByAuthor
|
||||
let loadByIdAndAuthor: VideoChannelMethods.LoadByIdAndAuthor
|
||||
let loadByUUID: VideoChannelMethods.LoadByUUID
|
||||
let loadAndPopulateAuthor: VideoChannelMethods.LoadAndPopulateAuthor
|
||||
let loadByUUIDAndPopulateAuthor: VideoChannelMethods.LoadByUUIDAndPopulateAuthor
|
||||
let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID
|
||||
let loadAndPopulateAuthorAndVideos: VideoChannelMethods.LoadAndPopulateAuthorAndVideos
|
||||
|
||||
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
||||
VideoChannel = sequelize.define<VideoChannelInstance, VideoChannelAttributes>('VideoChannel',
|
||||
{
|
||||
uuid: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
isUUID: 4
|
||||
}
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
nameValid: value => {
|
||||
const res = isVideoChannelNameValid(value)
|
||||
if (res === false) throw new Error('Video channel name is not valid.')
|
||||
}
|
||||
}
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
validate: {
|
||||
descriptionValid: value => {
|
||||
const res = isVideoChannelDescriptionValid(value)
|
||||
if (res === false) throw new Error('Video channel description is not valid.')
|
||||
}
|
||||
}
|
||||
},
|
||||
remote: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
},
|
||||
{
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'authorId' ]
|
||||
}
|
||||
],
|
||||
hooks: {
|
||||
afterDestroy
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const classMethods = [
|
||||
associate,
|
||||
|
||||
listForApi,
|
||||
listByAuthor,
|
||||
listOwned,
|
||||
loadByIdAndAuthor,
|
||||
loadAndPopulateAuthor,
|
||||
loadByUUIDAndPopulateAuthor,
|
||||
loadByUUID,
|
||||
loadByHostAndUUID,
|
||||
loadAndPopulateAuthorAndVideos,
|
||||
countByAuthor
|
||||
]
|
||||
const instanceMethods = [
|
||||
isOwned,
|
||||
toFormattedJSON,
|
||||
toAddRemoteJSON,
|
||||
toUpdateRemoteJSON
|
||||
]
|
||||
addMethodsToModel(VideoChannel, classMethods, instanceMethods)
|
||||
|
||||
return VideoChannel
|
||||
}
|
||||
|
||||
// ------------------------------ METHODS ------------------------------
|
||||
|
||||
isOwned = function (this: VideoChannelInstance) {
|
||||
return this.remote === false
|
||||
}
|
||||
|
||||
toFormattedJSON = function (this: VideoChannelInstance) {
|
||||
const json = {
|
||||
id: this.id,
|
||||
uuid: this.uuid,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
isLocal: this.isOwned(),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
|
||||
if (this.Author !== undefined) {
|
||||
json['owner'] = {
|
||||
name: this.Author.name,
|
||||
uuid: this.Author.uuid
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(this.Videos)) {
|
||||
json['videos'] = this.Videos.map(v => v.toFormattedJSON())
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
toAddRemoteJSON = function (this: VideoChannelInstance) {
|
||||
const json = {
|
||||
uuid: this.uuid,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
ownerUUID: this.Author.uuid
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
toUpdateRemoteJSON = function (this: VideoChannelInstance) {
|
||||
const json = {
|
||||
uuid: this.uuid,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
ownerUUID: this.Author.uuid
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
// ------------------------------ STATICS ------------------------------
|
||||
|
||||
function associate (models) {
|
||||
VideoChannel.belongsTo(models.Author, {
|
||||
foreignKey: {
|
||||
name: 'authorId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
|
||||
VideoChannel.hasMany(models.Video, {
|
||||
foreignKey: {
|
||||
name: 'channelId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
}
|
||||
|
||||
function afterDestroy (videoChannel: VideoChannelInstance, options: { transaction: Sequelize.Transaction }) {
|
||||
if (videoChannel.isOwned()) {
|
||||
const removeVideoChannelToFriendsParams = {
|
||||
uuid: videoChannel.uuid
|
||||
}
|
||||
|
||||
return removeVideoChannelToFriends(removeVideoChannelToFriendsParams, options.transaction)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
countByAuthor = function (authorId: number) {
|
||||
const query = {
|
||||
where: {
|
||||
authorId
|
||||
}
|
||||
}
|
||||
|
||||
return VideoChannel.count(query)
|
||||
}
|
||||
|
||||
listOwned = function () {
|
||||
const query = {
|
||||
where: {
|
||||
remote: false
|
||||
},
|
||||
include: [ VideoChannel['sequelize'].models.Author ]
|
||||
}
|
||||
|
||||
return VideoChannel.findAll(query)
|
||||
}
|
||||
|
||||
listForApi = function (start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
include: [
|
||||
{
|
||||
model: VideoChannel['sequelize'].models.Author,
|
||||
required: true,
|
||||
include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoChannel.findAndCountAll(query).then(({ rows, count }) => {
|
||||
return { total: count, data: rows }
|
||||
})
|
||||
}
|
||||
|
||||
listByAuthor = function (authorId: number) {
|
||||
const query = {
|
||||
order: [ getSort('createdAt') ],
|
||||
include: [
|
||||
{
|
||||
model: VideoChannel['sequelize'].models.Author,
|
||||
where: {
|
||||
id: authorId
|
||||
},
|
||||
required: true,
|
||||
include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoChannel.findAndCountAll(query).then(({ rows, count }) => {
|
||||
return { total: count, data: rows }
|
||||
})
|
||||
}
|
||||
|
||||
loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
|
||||
const query: Sequelize.FindOptions<VideoChannelAttributes> = {
|
||||
where: {
|
||||
uuid
|
||||
}
|
||||
}
|
||||
|
||||
if (t !== undefined) query.transaction = t
|
||||
|
||||
return VideoChannel.findOne(query)
|
||||
}
|
||||
|
||||
loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
|
||||
const query: Sequelize.FindOptions<VideoChannelAttributes> = {
|
||||
where: {
|
||||
uuid
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: VideoChannel['sequelize'].models.Author,
|
||||
include: [
|
||||
{
|
||||
model: VideoChannel['sequelize'].models.Pod,
|
||||
required: true,
|
||||
where: {
|
||||
host: fromHost
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if (t !== undefined) query.transaction = t
|
||||
|
||||
return VideoChannel.findOne(query)
|
||||
}
|
||||
|
||||
loadByIdAndAuthor = function (id: number, authorId: number) {
|
||||
const options = {
|
||||
where: {
|
||||
id,
|
||||
authorId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: VideoChannel['sequelize'].models.Author,
|
||||
include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoChannel.findOne(options)
|
||||
}
|
||||
|
||||
loadAndPopulateAuthor = function (id: number) {
|
||||
const options = {
|
||||
include: [
|
||||
{
|
||||
model: VideoChannel['sequelize'].models.Author,
|
||||
include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoChannel.findById(id, options)
|
||||
}
|
||||
|
||||
loadByUUIDAndPopulateAuthor = function (uuid: string) {
|
||||
const options = {
|
||||
where: {
|
||||
uuid
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: VideoChannel['sequelize'].models.Author,
|
||||
include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoChannel.findOne(options)
|
||||
}
|
||||
|
||||
loadAndPopulateAuthorAndVideos = function (id: number) {
|
||||
const options = {
|
||||
include: [
|
||||
{
|
||||
model: VideoChannel['sequelize'].models.Author,
|
||||
include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
|
||||
},
|
||||
VideoChannel['sequelize'].models.Video
|
||||
]
|
||||
}
|
||||
|
||||
return VideoChannel.findById(id, options)
|
||||
}
|
|
@ -6,16 +6,21 @@ import { TagAttributes, TagInstance } from './tag-interface'
|
|||
import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
|
||||
|
||||
// Don't use barrel, import just what we need
|
||||
import { Video as FormattedVideo } from '../../../shared/models/videos/video.model'
|
||||
import {
|
||||
Video as FormattedVideo,
|
||||
VideoDetails as FormattedDetailsVideo
|
||||
} from '../../../shared/models/videos/video.model'
|
||||
import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model'
|
||||
import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model'
|
||||
import { ResultList } from '../../../shared/models/result-list.model'
|
||||
import { VideoChannelInstance } from './video-channel-interface'
|
||||
|
||||
export namespace VideoMethods {
|
||||
export type GetThumbnailName = (this: VideoInstance) => string
|
||||
export type GetPreviewName = (this: VideoInstance) => string
|
||||
export type IsOwned = (this: VideoInstance) => boolean
|
||||
export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo
|
||||
export type ToFormattedDetailsJSON = (this: VideoInstance) => FormattedDetailsVideo
|
||||
|
||||
export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance
|
||||
export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
||||
|
@ -52,8 +57,8 @@ export namespace VideoMethods {
|
|||
) => Promise< ResultList<VideoInstance> >
|
||||
|
||||
export type Load = (id: number) => Promise<VideoInstance>
|
||||
export type LoadByUUID = (uuid: string) => Promise<VideoInstance>
|
||||
export type LoadByHostAndUUID = (fromHost: string, uuid: string) => Promise<VideoInstance>
|
||||
export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance>
|
||||
export type LoadByHostAndUUID = (fromHost: string, uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance>
|
||||
export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance>
|
||||
export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance>
|
||||
export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance>
|
||||
|
@ -94,7 +99,9 @@ export interface VideoAttributes {
|
|||
dislikes?: number
|
||||
remote: boolean
|
||||
|
||||
Author?: AuthorInstance
|
||||
channelId?: number
|
||||
|
||||
VideoChannel?: VideoChannelInstance
|
||||
Tags?: TagInstance[]
|
||||
VideoFiles?: VideoFileInstance[]
|
||||
}
|
||||
|
@ -121,6 +128,7 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
|
|||
removeTorrent: VideoMethods.RemoveTorrent
|
||||
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||
toFormattedJSON: VideoMethods.ToFormattedJSON
|
||||
toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON
|
||||
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||
optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
|
||||
transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
|
||||
|
|
|
@ -60,6 +60,7 @@ let getPreviewPath: VideoMethods.GetPreviewPath
|
|||
let getTorrentFileName: VideoMethods.GetTorrentFileName
|
||||
let isOwned: VideoMethods.IsOwned
|
||||
let toFormattedJSON: VideoMethods.ToFormattedJSON
|
||||
let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON
|
||||
let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||
let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||
let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
|
||||
|
@ -205,9 +206,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
},
|
||||
{
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'authorId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'name' ]
|
||||
},
|
||||
|
@ -225,6 +223,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
},
|
||||
{
|
||||
fields: [ 'uuid' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'channelId' ]
|
||||
}
|
||||
],
|
||||
hooks: {
|
||||
|
@ -268,6 +269,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
removeTorrent,
|
||||
toAddRemoteJSON,
|
||||
toFormattedJSON,
|
||||
toFormattedDetailsJSON,
|
||||
toUpdateRemoteJSON,
|
||||
optimizeOriginalVideofile,
|
||||
transcodeOriginalVideofile,
|
||||
|
@ -282,9 +284,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
// ------------------------------ METHODS ------------------------------
|
||||
|
||||
function associate (models) {
|
||||
Video.belongsTo(models.Author, {
|
||||
Video.belongsTo(models.VideoChannel, {
|
||||
foreignKey: {
|
||||
name: 'authorId',
|
||||
name: 'channelId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
|
@ -439,8 +441,8 @@ getPreviewPath = function (this: VideoInstance) {
|
|||
toFormattedJSON = function (this: VideoInstance) {
|
||||
let podHost
|
||||
|
||||
if (this.Author.Pod) {
|
||||
podHost = this.Author.Pod.host
|
||||
if (this.VideoChannel.Author.Pod) {
|
||||
podHost = this.VideoChannel.Author.Pod.host
|
||||
} else {
|
||||
// It means it's our video
|
||||
podHost = CONFIG.WEBSERVER.HOST
|
||||
|
@ -472,7 +474,59 @@ toFormattedJSON = function (this: VideoInstance) {
|
|||
description: this.description,
|
||||
podHost,
|
||||
isLocal: this.isOwned(),
|
||||
author: this.Author.name,
|
||||
author: this.VideoChannel.Author.name,
|
||||
duration: this.duration,
|
||||
views: this.views,
|
||||
likes: this.likes,
|
||||
dislikes: this.dislikes,
|
||||
tags: map<TagInstance, string>(this.Tags, 'name'),
|
||||
thumbnailPath: this.getThumbnailPath(),
|
||||
previewPath: this.getPreviewPath(),
|
||||
embedPath: this.getEmbedPath(),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
toFormattedDetailsJSON = function (this: VideoInstance) {
|
||||
let podHost
|
||||
|
||||
if (this.VideoChannel.Author.Pod) {
|
||||
podHost = this.VideoChannel.Author.Pod.host
|
||||
} else {
|
||||
// It means it's our video
|
||||
podHost = CONFIG.WEBSERVER.HOST
|
||||
}
|
||||
|
||||
// Maybe our pod is not up to date and there are new categories since our version
|
||||
let categoryLabel = VIDEO_CATEGORIES[this.category]
|
||||
if (!categoryLabel) categoryLabel = 'Misc'
|
||||
|
||||
// Maybe our pod is not up to date and there are new licences since our version
|
||||
let licenceLabel = VIDEO_LICENCES[this.licence]
|
||||
if (!licenceLabel) licenceLabel = 'Unknown'
|
||||
|
||||
// Language is an optional attribute
|
||||
let languageLabel = VIDEO_LANGUAGES[this.language]
|
||||
if (!languageLabel) languageLabel = 'Unknown'
|
||||
|
||||
const json = {
|
||||
id: this.id,
|
||||
uuid: this.uuid,
|
||||
name: this.name,
|
||||
category: this.category,
|
||||
categoryLabel,
|
||||
licence: this.licence,
|
||||
licenceLabel,
|
||||
language: this.language,
|
||||
languageLabel,
|
||||
nsfw: this.nsfw,
|
||||
description: this.description,
|
||||
podHost,
|
||||
isLocal: this.isOwned(),
|
||||
author: this.VideoChannel.Author.name,
|
||||
duration: this.duration,
|
||||
views: this.views,
|
||||
likes: this.likes,
|
||||
|
@ -483,6 +537,7 @@ toFormattedJSON = function (this: VideoInstance) {
|
|||
embedPath: this.getEmbedPath(),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
channel: this.VideoChannel.toFormattedJSON(),
|
||||
files: []
|
||||
}
|
||||
|
||||
|
@ -525,7 +580,7 @@ toAddRemoteJSON = function (this: VideoInstance) {
|
|||
language: this.language,
|
||||
nsfw: this.nsfw,
|
||||
description: this.description,
|
||||
author: this.Author.name,
|
||||
channelUUID: this.VideoChannel.uuid,
|
||||
duration: this.duration,
|
||||
thumbnailData: thumbnailData.toString('binary'),
|
||||
tags: map<TagInstance, string>(this.Tags, 'name'),
|
||||
|
@ -559,7 +614,6 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
|
|||
language: this.language,
|
||||
nsfw: this.nsfw,
|
||||
description: this.description,
|
||||
author: this.Author.name,
|
||||
duration: this.duration,
|
||||
tags: map<TagInstance, string>(this.Tags, 'name'),
|
||||
createdAt: this.createdAt,
|
||||
|
@ -723,8 +777,18 @@ listForApi = function (start: number, count: number, sort: string) {
|
|||
order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Pod,
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
|
@ -740,8 +804,8 @@ listForApi = function (start: number, count: number, sort: string) {
|
|||
})
|
||||
}
|
||||
|
||||
loadByHostAndUUID = function (fromHost: string, uuid: string) {
|
||||
const query = {
|
||||
loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
|
||||
const query: Sequelize.FindOptions<VideoAttributes> = {
|
||||
where: {
|
||||
uuid
|
||||
},
|
||||
|
@ -750,20 +814,27 @@ loadByHostAndUUID = function (fromHost: string, uuid: string) {
|
|||
model: Video['sequelize'].models.VideoFile
|
||||
},
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Pod,
|
||||
required: true,
|
||||
where: {
|
||||
host: fromHost
|
||||
}
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Pod,
|
||||
required: true,
|
||||
where: {
|
||||
host: fromHost
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if (t !== undefined) query.transaction = t
|
||||
|
||||
return Video.findOne(query)
|
||||
}
|
||||
|
||||
|
@ -774,7 +845,10 @@ listOwnedAndPopulateAuthorAndTags = function () {
|
|||
},
|
||||
include: [
|
||||
Video['sequelize'].models.VideoFile,
|
||||
Video['sequelize'].models.Author,
|
||||
{
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
include: [ Video['sequelize'].models.Author ]
|
||||
},
|
||||
Video['sequelize'].models.Tag
|
||||
]
|
||||
}
|
||||
|
@ -792,10 +866,15 @@ listOwnedByAuthor = function (author: string) {
|
|||
model: Video['sequelize'].models.VideoFile
|
||||
},
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
where: {
|
||||
name: author
|
||||
}
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
where: {
|
||||
name: author
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -807,19 +886,28 @@ load = function (id: number) {
|
|||
return Video.findById(id)
|
||||
}
|
||||
|
||||
loadByUUID = function (uuid: string) {
|
||||
const query = {
|
||||
loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
|
||||
const query: Sequelize.FindOptions<VideoAttributes> = {
|
||||
where: {
|
||||
uuid
|
||||
},
|
||||
include: [ Video['sequelize'].models.VideoFile ]
|
||||
}
|
||||
|
||||
if (t !== undefined) query.transaction = t
|
||||
|
||||
return Video.findOne(query)
|
||||
}
|
||||
|
||||
loadAndPopulateAuthor = function (id: number) {
|
||||
const options = {
|
||||
include: [ Video['sequelize'].models.VideoFile, Video['sequelize'].models.Author ]
|
||||
include: [
|
||||
Video['sequelize'].models.VideoFile,
|
||||
{
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
include: [ Video['sequelize'].models.Author ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return Video.findById(id, options)
|
||||
|
@ -829,8 +917,13 @@ loadAndPopulateAuthorAndPodAndTags = function (id: number) {
|
|||
const options = {
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||
}
|
||||
]
|
||||
},
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
|
@ -847,8 +940,13 @@ loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) {
|
|||
},
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||
}
|
||||
]
|
||||
},
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
|
@ -866,9 +964,13 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
|
||||
const authorInclude: Sequelize.IncludeOptions = {
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [
|
||||
podInclude
|
||||
]
|
||||
include: [ podInclude ]
|
||||
}
|
||||
|
||||
const videoChannelInclude: Sequelize.IncludeOptions = {
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
include: [ authorInclude ],
|
||||
required: true
|
||||
}
|
||||
|
||||
const tagInclude: Sequelize.IncludeOptions = {
|
||||
|
@ -915,8 +1017,6 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
$iLike: '%' + value + '%'
|
||||
}
|
||||
}
|
||||
|
||||
// authorInclude.or = true
|
||||
} else {
|
||||
query.where[field] = {
|
||||
$iLike: '%' + value + '%'
|
||||
|
@ -924,7 +1024,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
}
|
||||
|
||||
query.include = [
|
||||
authorInclude, tagInclude, videoFileInclude
|
||||
videoChannelInclude, tagInclude, videoFileInclude
|
||||
]
|
||||
|
||||
return Video.findAndCountAll(query).then(({ rows, count }) => {
|
||||
|
@ -955,8 +1055,8 @@ function getBaseUrls (video: VideoInstance) {
|
|||
baseUrlHttp = CONFIG.WEBSERVER.URL
|
||||
baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
|
||||
} else {
|
||||
baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host
|
||||
baseUrlWs = REMOTE_SCHEME.WS + '://' + video.Author.Pod.host
|
||||
baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Author.Pod.host
|
||||
baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Author.Pod.host
|
||||
}
|
||||
|
||||
return { baseUrlHttp, baseUrlWs }
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
export * from './remote-qadu-video-request.model'
|
||||
export * from './remote-video-author-create-request.model'
|
||||
export * from './remote-video-author-remove-request.model'
|
||||
export * from './remote-video-event-request.model'
|
||||
export * from './remote-video-request.model'
|
||||
export * from './remote-video-create-request.model'
|
||||
export * from './remote-video-update-request.model'
|
||||
export * from './remote-video-remove-request.model'
|
||||
export * from './remote-video-channel-create-request.model'
|
||||
export * from './remote-video-channel-update-request.model'
|
||||
export * from './remote-video-channel-remove-request.model'
|
||||
export * from './remote-video-report-abuse-request.model'
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { RemoteVideoRequest } from './remote-video-request.model'
|
||||
|
||||
export interface RemoteVideoAuthorCreateData {
|
||||
uuid: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface RemoteVideoAuthorCreateRequest extends RemoteVideoRequest {
|
||||
type: 'add-author'
|
||||
data: RemoteVideoAuthorCreateData
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { RemoteVideoRequest } from './remote-video-request.model'
|
||||
|
||||
export interface RemoteVideoAuthorRemoveData {
|
||||
uuid: string
|
||||
}
|
||||
|
||||
export interface RemoteVideoAuthorRemoveRequest extends RemoteVideoRequest {
|
||||
type: 'remove-author'
|
||||
data: RemoteVideoAuthorRemoveData
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { RemoteVideoRequest } from './remote-video-request.model'
|
||||
|
||||
export interface RemoteVideoChannelCreateData {
|
||||
uuid: string
|
||||
name: string
|
||||
description: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
ownerUUID: string
|
||||
}
|
||||
|
||||
export interface RemoteVideoChannelCreateRequest extends RemoteVideoRequest {
|
||||
type: 'add-channel'
|
||||
data: RemoteVideoChannelCreateData
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { RemoteVideoRequest } from './remote-video-request.model'
|
||||
|
||||
export interface RemoteVideoChannelRemoveData {
|
||||
uuid: string
|
||||
}
|
||||
|
||||
export interface RemoteVideoChannelRemoveRequest extends RemoteVideoRequest {
|
||||
type: 'remove-channel'
|
||||
data: RemoteVideoChannelRemoveData
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { RemoteVideoRequest } from './remote-video-request.model'
|
||||
|
||||
export interface RemoteVideoChannelUpdateData {
|
||||
uuid: string
|
||||
name: string
|
||||
description: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
ownerUUID: string
|
||||
}
|
||||
|
||||
export interface RemoteVideoChannelUpdateRequest extends RemoteVideoRequest {
|
||||
type: 'update-channel'
|
||||
data: RemoteVideoChannelUpdateData
|
||||
}
|
|
@ -2,7 +2,7 @@ import { RemoteVideoRequest } from './remote-video-request.model'
|
|||
|
||||
export interface RemoteVideoCreateData {
|
||||
uuid: string
|
||||
author: string
|
||||
channelUUID: string
|
||||
tags: string[]
|
||||
name: string
|
||||
category: number
|
||||
|
@ -26,6 +26,6 @@ export interface RemoteVideoCreateData {
|
|||
}
|
||||
|
||||
export interface RemoteVideoCreateRequest extends RemoteVideoRequest {
|
||||
type: 'add'
|
||||
type: 'add-video'
|
||||
data: RemoteVideoCreateData
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ export interface RemoteVideoRemoveData {
|
|||
}
|
||||
|
||||
export interface RemoteVideoRemoveRequest extends RemoteVideoRequest {
|
||||
type: 'remove'
|
||||
type: 'remove-video'
|
||||
data: RemoteVideoRemoveData
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
export interface RemoteVideoRequest {
|
||||
type: 'add' | 'update' | 'remove' | 'report-abuse'
|
||||
type: RemoteVideoRequestType
|
||||
data: any
|
||||
}
|
||||
|
||||
export type RemoteVideoRequestType = 'add-video' | 'update-video' | 'remove-video' |
|
||||
'add-channel' | 'update-channel' | 'remove-channel' |
|
||||
'report-abuse' |
|
||||
'add-author' | 'remove-author'
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { RemoteVideoRequest } from './remote-video-request.model'
|
||||
|
||||
export interface RemoteVideoUpdateData {
|
||||
uuid: string
|
||||
tags: string[]
|
||||
|
@ -21,7 +23,7 @@ export interface RemoteVideoUpdateData {
|
|||
}[]
|
||||
}
|
||||
|
||||
export interface RemoteVideoUpdateRequest {
|
||||
type: 'update'
|
||||
export interface RemoteVideoUpdateRequest extends RemoteVideoRequest {
|
||||
type: 'update-video'
|
||||
data: RemoteVideoUpdateData
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { UserRole } from './user-role.type'
|
||||
import { VideoChannel } from '../videos/video-channel.model'
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
|
@ -7,5 +8,10 @@ export interface User {
|
|||
displayNSFW: boolean
|
||||
role: UserRole
|
||||
videoQuota: number
|
||||
createdAt: Date
|
||||
createdAt: Date,
|
||||
author: {
|
||||
id: number
|
||||
uuid: string
|
||||
}
|
||||
videoChannels?: VideoChannel[]
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ export * from './user-video-rate.type'
|
|||
export * from './video-abuse-create.model'
|
||||
export * from './video-abuse.model'
|
||||
export * from './video-blacklist.model'
|
||||
export * from './video-channel-create.model'
|
||||
export * from './video-channel-update.model'
|
||||
export * from './video-channel.model'
|
||||
export * from './video-create.model'
|
||||
export * from './video-rate.type'
|
||||
export * from './video-resolution.enum'
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export interface VideoChannelCreate {
|
||||
name: string
|
||||
description?: string
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface VideoChannelUpdate {
|
||||
name: string
|
||||
description: string
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { Video } from './video.model'
|
||||
|
||||
export interface VideoChannel {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
isLocal: boolean
|
||||
createdAt: Date | string
|
||||
updatedAt: Date | string
|
||||
owner?: {
|
||||
name: string
|
||||
uuid: string
|
||||
}
|
||||
videos?: Video[]
|
||||
}
|
|
@ -3,6 +3,7 @@ export interface VideoCreate {
|
|||
licence: number
|
||||
language: number
|
||||
description: string
|
||||
channelId: number
|
||||
nsfw: boolean
|
||||
name: string
|
||||
tags: string[]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { VideoChannel } from './video-channel.model'
|
||||
|
||||
export interface VideoFile {
|
||||
magnetUri: string
|
||||
resolution: number
|
||||
|
@ -32,5 +34,9 @@ export interface Video {
|
|||
likes: number
|
||||
dislikes: number
|
||||
nsfw: boolean
|
||||
}
|
||||
|
||||
export interface VideoDetails extends Video {
|
||||
channel: VideoChannel
|
||||
files: VideoFile[]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue