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