setBodyHostPort,
remotePodsAddValidator
} from '../../../middlewares'
-import { sendOwnedVideosToPod } from '../../../lib'
+import { sendOwnedDataToPod } from '../../../lib'
import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
import { CONFIG } from '../../../initializers'
import { PodInstance } from '../../../models'
const pod = db.Pod.build(information)
pod.save()
.then(podCreated => {
- return sendOwnedVideosToPod(podCreated.id)
+ return sendOwnedDataToPod(podCreated.id)
})
.then(() => {
return getMyPublicCert()
import * as express from 'express'
import * as Promise from 'bluebird'
+import * as Sequelize from 'sequelize'
import { database as db } from '../../../initializers/database'
import {
RemoteQaduVideoRequest,
RemoteQaduVideoData,
RemoteVideoEventRequest,
- RemoteVideoEventData
+ RemoteVideoEventData,
+ RemoteVideoChannelCreateData,
+ RemoteVideoChannelUpdateData,
+ RemoteVideoChannelRemoveData,
+ RemoteVideoAuthorRemoveData,
+ RemoteVideoAuthorCreateData
} from '../../../../shared'
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
// Functions to call when processing a remote request
+// FIXME: use RemoteVideoRequestType as id type
const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
-functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo
-functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideo
+functionsHash[ENDPOINT_ACTIONS.ADD_VIDEO] = addRemoteVideoRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.UPDATE_VIDEO] = updateRemoteVideoRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.REMOVE_VIDEO] = removeRemoteVideoRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.ADD_CHANNEL] = addRemoteVideoChannelRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper
const remoteVideosRouter = express.Router()
function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
return db.sequelize.transaction(t => {
- return fetchVideoByUUID(eventData.uuid)
+ return fetchVideoByUUID(eventData.uuid, t)
.then(videoInstance => {
const options = { transaction: t }
let videoUUID = ''
return db.sequelize.transaction(t => {
- return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid)
+ return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
.then(videoInstance => {
const options = { transaction: t }
.then(video => {
if (video) throw new Error('UUID already exists.')
- return undefined
+ return db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t)
})
- .then(() => {
- const name = videoToCreateData.author
- const podId = fromPod.id
- // This author is from another pod so we do not associate a user
- const userId = null
+ .then(videoChannel => {
+ if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
- return db.Author.findOrCreateAuthor(name, podId, userId, t)
- })
- .then(author => {
const tags = videoToCreateData.tags
- return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
+ return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoChannel, tagInstances }))
})
- .then(({ author, tagInstances }) => {
+ .then(({ videoChannel, tagInstances }) => {
const videoData = {
name: videoToCreateData.name,
uuid: videoToCreateData.uuid,
language: videoToCreateData.language,
nsfw: videoToCreateData.nsfw,
description: videoToCreateData.description,
- authorId: author.id,
+ channelId: videoChannel.id,
duration: videoToCreateData.duration,
createdAt: videoToCreateData.createdAt,
// FIXME: updatedAt does not seems to be considered by Sequelize
logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
return db.sequelize.transaction(t => {
- return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid)
+ return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t)
.then(videoInstance => {
const tags = videoAttributesToUpdate.tags
// Remove old video files
videoInstance.VideoFiles.forEach(videoFile => {
- tasks.push(videoFile.destroy())
+ tasks.push(videoFile.destroy({ transaction: t }))
})
return Promise.all(tasks).then(() => ({ tagInstances, videoInstance }))
})
}
+function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
+ const options = {
+ arguments: [ videoToRemoveData, fromPod ],
+ errorMessage: 'Cannot remove the remote video channel with many retries.'
+ }
+
+ return retryTransactionWrapper(removeRemoteVideo, options)
+}
+
function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
- // We need the instance because we have to remove some other stuffs (thumbnail etc)
- return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid)
- .then(video => {
- logger.debug('Removing remote video with uuid %s.', video.uuid)
- return video.destroy()
- })
+ logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
+
+ return db.sequelize.transaction(t => {
+ // We need the instance because we have to remove some other stuffs (thumbnail etc)
+ return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
+ .then(video => video.destroy({ transaction: t }))
+ })
+ .then(() => logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid))
+ .catch(err => {
+ logger.debug('Cannot remove the remote video.', err)
+ throw err
+ })
+}
+
+function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
+ const options = {
+ arguments: [ authorToCreateData, fromPod ],
+ errorMessage: 'Cannot insert the remote video author with many retries.'
+ }
+
+ return retryTransactionWrapper(addRemoteVideoAuthor, options)
+}
+
+function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
+ logger.debug('Adding remote video author "%s".', authorToCreateData.uuid)
+
+ return db.sequelize.transaction(t => {
+ return db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
+ .then(author => {
+ if (author) throw new Error('UUID already exists.')
+
+ return undefined
+ })
+ .then(() => {
+ const videoAuthorData = {
+ name: authorToCreateData.name,
+ uuid: authorToCreateData.uuid,
+ userId: null, // Not on our pod
+ podId: fromPod.id
+ }
+
+ const author = db.Author.build(videoAuthorData)
+ return author.save({ transaction: t })
+ })
+ })
+ .then(() => logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid))
.catch(err => {
- logger.debug('Could not fetch remote video.', { host: fromPod.host, uuid: videoToRemoveData.uuid, error: err.stack })
+ logger.debug('Cannot insert the remote video author.', err)
+ throw err
})
}
+function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
+ const options = {
+ arguments: [ authorAttributesToRemove, fromPod ],
+ errorMessage: 'Cannot remove the remote video author with many retries.'
+ }
+
+ return retryTransactionWrapper(removeRemoteVideoAuthor, options)
+}
+
+function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
+ logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
+
+ return db.sequelize.transaction(t => {
+ return db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
+ .then(videoAuthor => videoAuthor.destroy({ transaction: t }))
+ })
+ .then(() => logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid))
+ .catch(err => {
+ logger.debug('Cannot remove the remote video author.', err)
+ throw err
+ })
+}
+
+function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
+ const options = {
+ arguments: [ videoChannelToCreateData, fromPod ],
+ errorMessage: 'Cannot insert the remote video channel with many retries.'
+ }
+
+ return retryTransactionWrapper(addRemoteVideoChannel, options)
+}
+
+function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
+ logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
+
+ return db.sequelize.transaction(t => {
+ return db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
+ .then(videoChannel => {
+ if (videoChannel) throw new Error('UUID already exists.')
+
+ return undefined
+ })
+ .then(() => {
+ const authorUUID = videoChannelToCreateData.ownerUUID
+ const podId = fromPod.id
+
+ return db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
+ })
+ .then(author => {
+ if (!author) throw new Error('Unknown author UUID.')
+
+ const videoChannelData = {
+ name: videoChannelToCreateData.name,
+ description: videoChannelToCreateData.description,
+ uuid: videoChannelToCreateData.uuid,
+ createdAt: videoChannelToCreateData.createdAt,
+ updatedAt: videoChannelToCreateData.updatedAt,
+ remote: true,
+ authorId: author.id
+ }
+
+ const videoChannel = db.VideoChannel.build(videoChannelData)
+ return videoChannel.save({ transaction: t })
+ })
+ })
+ .then(() => logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid))
+ .catch(err => {
+ logger.debug('Cannot insert the remote video channel.', err)
+ throw err
+ })
+}
+
+function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
+ const options = {
+ arguments: [ videoChannelAttributesToUpdate, fromPod ],
+ errorMessage: 'Cannot update the remote video channel with many retries.'
+ }
+
+ return retryTransactionWrapper(updateRemoteVideoChannel, options)
+}
+
+function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
+ logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid)
+
+ return db.sequelize.transaction(t => {
+ return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
+ .then(videoChannelInstance => {
+ const options = { transaction: t }
+
+ videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
+ videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
+ videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
+ videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
+
+ return videoChannelInstance.save(options)
+ })
+ })
+ .then(() => logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid))
+ .catch(err => {
+ // This is just a debug because we will retry the insert
+ logger.debug('Cannot update the remote video channel.', err)
+ throw err
+ })
+}
+
+function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
+ const options = {
+ arguments: [ videoChannelAttributesToRemove, fromPod ],
+ errorMessage: 'Cannot remove the remote video channel with many retries.'
+ }
+
+ return retryTransactionWrapper(removeRemoteVideoChannel, options)
+}
+
+function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
+ logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
+
+ return db.sequelize.transaction(t => {
+ return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
+ .then(videoChannel => videoChannel.destroy({ transaction: t }))
+ })
+ .then(() => logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid))
+ .catch(err => {
+ logger.debug('Cannot remove the remote video channel.', err)
+ throw err
+ })
+}
+
+function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
+ const options = {
+ arguments: [ reportData, fromPod ],
+ errorMessage: 'Cannot create remote abuse video with many retries.'
+ }
+
+ return retryTransactionWrapper(reportAbuseRemoteVideo, options)
+}
+
function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
- return fetchVideoByUUID(reportData.videoUUID)
- .then(video => {
- logger.debug('Reporting remote abuse for video %s.', video.id)
+ logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
- const videoAbuseData = {
- reporterUsername: reportData.reporterUsername,
- reason: reportData.reportReason,
- reporterPodId: fromPod.id,
- videoId: video.id
- }
+ return db.sequelize.transaction(t => {
+ return fetchVideoByUUID(reportData.videoUUID, t)
+ .then(video => {
+ const videoAbuseData = {
+ reporterUsername: reportData.reporterUsername,
+ reason: reportData.reportReason,
+ reporterPodId: fromPod.id,
+ videoId: video.id
+ }
- return db.VideoAbuse.create(videoAbuseData)
- })
- .catch(err => logger.error('Cannot create remote abuse video.', err))
+ return db.VideoAbuse.create(videoAbuseData)
+ })
+ })
+ .then(() => logger.info('Remote abuse for video uuid %s created', reportData.videoUUID))
+ .catch(err => {
+ // This is just a debug because we will retry the insert
+ logger.debug('Cannot create remote abuse video', err)
+ throw err
+ })
}
-function fetchVideoByUUID (id: string) {
- return db.Video.loadByUUID(id)
+function fetchVideoByUUID (id: string, t: Sequelize.Transaction) {
+ return db.Video.loadByUUID(id, t)
.then(video => {
if (!video) throw new Error('Video not found')
})
}
-function fetchVideoByHostAndUUID (podHost: string, uuid: string) {
- return db.Video.loadByHostAndUUID(podHost, uuid)
+function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
+ return db.Video.loadByHostAndUUID(podHost, uuid, t)
.then(video => {
if (!video) throw new Error('Video not found')
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
+ })
+}
import { database as db } from '../../initializers/database'
import { USER_ROLES, CONFIG } from '../../initializers'
-import { logger, getFormattedObjects } from '../../helpers'
+import { logger, getFormattedObjects, retryTransactionWrapper } from '../../helpers'
import {
authenticate,
ensureIsAdmin,
UserUpdate,
UserUpdateMe
} from '../../../shared'
+import { createUserAuthorAndChannel } from '../../lib'
import { UserInstance } from '../../models'
const usersRouter = express.Router()
authenticate,
ensureIsAdmin,
usersAddValidator,
- createUser
+ createUserRetryWrapper
)
usersRouter.post('/register',
// ---------------------------------------------------------------------------
+function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const options = {
+ arguments: [ req, res ],
+ errorMessage: 'Cannot insert the user with many retries.'
+ }
+
+ retryTransactionWrapper(createUser, options)
+ .then(() => {
+ // TODO : include Location of the new user -> 201
+ res.type('json').status(204).end()
+ })
+ .catch(err => next(err))
+}
+
function createUser (req: express.Request, res: express.Response, next: express.NextFunction) {
const body: UserCreate = req.body
-
const user = db.User.build({
username: body.username,
password: body.password,
videoQuota: body.videoQuota
})
- user.save()
- .then(() => res.type('json').status(204).end())
- .catch(err => next(err))
+ return createUserAuthorAndChannel(user)
+ .then(() => logger.info('User %s with its channel and author created.', body.username))
+ .catch((err: Error) => {
+ logger.debug('Cannot insert the user.', err)
+ throw err
+ })
}
function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) {
videoQuota: CONFIG.USER.VIDEO_QUOTA
})
- user.save()
+ return createUserAuthorAndChannel(user)
.then(() => res.type('json').status(204).end())
.catch(err => next(err))
}
function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
- db.User.loadByUsername(res.locals.oauth.token.user.username)
+ db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
.then(user => res.json(user.toFormattedJSON()))
.catch(err => next(err))
}
--- /dev/null
+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))
+}
import { abuseVideoRouter } from './abuse'
import { blacklistRouter } from './blacklist'
import { rateVideoRouter } from './rate'
+import { videoChannelRouter } from './channel'
const videosRouter = express.Router()
videosRouter.use('/', abuseVideoRouter)
videosRouter.use('/', blacklistRouter)
videosRouter.use('/', rateVideoRouter)
+videosRouter.use('/', videoChannelRouter)
videosRouter.get('/categories', listVideoCategories)
videosRouter.get('/licences', listVideoLicences)
let videoUUID = ''
return db.sequelize.transaction(t => {
- const user = res.locals.oauth.token.User
+ let p: Promise<TagInstance[]>
- const name = user.username
- // null because it is OUR pod
- const podId = null
- const userId = user.id
+ if (!videoInfo.tags) p = Promise.resolve(undefined)
+ else p = db.Tag.findOrCreateTags(videoInfo.tags, t)
- return db.Author.findOrCreateAuthor(name, podId, userId, t)
- .then(author => {
- const tags = videoInfo.tags
- if (!tags) return { author, tagInstances: undefined }
-
- return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
- })
- .then(({ author, tagInstances }) => {
+ return p
+ .then(tagInstances => {
const videoData = {
name: videoInfo.name,
remote: false,
nsfw: videoInfo.nsfw,
description: videoInfo.description,
duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
- authorId: author.id
+ channelId: res.locals.videoChannel.id
}
const video = db.Video.build(videoData)
- return { author, tagInstances, video }
+ return { tagInstances, video }
})
- .then(({ author, tagInstances, video }) => {
+ .then(({ tagInstances, video }) => {
const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
return getVideoFileHeight(videoFilePath)
- .then(height => ({ author, tagInstances, video, videoFileHeight: height }))
+ .then(height => ({ tagInstances, video, videoFileHeight: height }))
})
- .then(({ author, tagInstances, video, videoFileHeight }) => {
+ .then(({ tagInstances, video, videoFileHeight }) => {
const videoFileData = {
extname: extname(videoPhysicalFile.filename),
resolution: videoFileHeight,
}
const videoFile = db.VideoFile.build(videoFileData)
- return { author, tagInstances, video, videoFile }
+ return { tagInstances, video, videoFile }
})
- .then(({ author, tagInstances, video, videoFile }) => {
+ .then(({ tagInstances, video, videoFile }) => {
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
const source = join(videoDir, videoPhysicalFile.filename)
const destination = join(videoDir, video.getVideoFilename(videoFile))
.then(() => {
// This is important in case if there is another attempt in the retry process
videoPhysicalFile.filename = video.getVideoFilename(videoFile)
- return { author, tagInstances, video, videoFile }
+ return { tagInstances, video, videoFile }
})
})
- .then(({ author, tagInstances, video, videoFile }) => {
+ .then(({ tagInstances, video, videoFile }) => {
const tasks = []
tasks.push(
)
}
- return Promise.all(tasks).then(() => ({ author, tagInstances, video, videoFile }))
+ return Promise.all(tasks).then(() => ({ tagInstances, video, videoFile }))
})
- .then(({ author, tagInstances, video, videoFile }) => {
+ .then(({ tagInstances, video, videoFile }) => {
const options = { transaction: t }
return video.save(options)
.then(videoCreated => {
- // Do not forget to add Author information to the created video
- videoCreated.Author = author
+ // Do not forget to add video channel information to the created video
+ videoCreated.VideoChannel = res.locals.videoChannel
videoUUID = videoCreated.uuid
return { tagInstances, video: videoCreated, videoFile }
}
// 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) {
width: embedWidth,
height: embedHeight,
title: video.name,
- author_name: video.Author.name,
+ author_name: video.VideoChannel.Author.name,
provider_name: 'PeerTube',
provider_url: webserverUrl
}
export * from './pods'
export * from './pods'
export * from './users'
+export * from './video-authors'
+export * from './video-channels'
export * from './videos'
-import 'express-validator'
+import * as validator from 'validator'
function exists (value: any) {
return value !== undefined && value !== null
return Array.isArray(value)
}
+function isDateValid (value: string) {
+ return exists(value) && validator.isISO8601(value)
+}
+
+function isIdValid (value: string) {
+ return exists(value) && validator.isInt('' + value)
+}
+
+function isUUIDValid (value: string) {
+ return exists(value) && validator.isUUID('' + value, 4)
+}
+
+function isIdOrUUIDValid (value: string) {
+ return isIdValid(value) || isUUIDValid(value)
+}
+
// ---------------------------------------------------------------------------
export {
exists,
- isArray
+ isArray,
+ isIdValid,
+ isUUIDValid,
+ isIdOrUUIDValid,
+ isDateValid
}
REQUEST_ENDPOINT_ACTIONS,
REQUEST_VIDEO_EVENT_TYPES
} from '../../../initializers'
-import { isArray } from '../misc'
+import { isArray, isDateValid, isUUIDValid } from '../misc'
import {
- isVideoAuthorValid,
isVideoThumbnailDataValid,
- isVideoUUIDValid,
isVideoAbuseReasonValid,
isVideoAbuseReporterUsernameValid,
isVideoViewsValid,
isVideoLikesValid,
isVideoDislikesValid,
isVideoEventCountValid,
- isVideoDateValid,
isVideoCategoryValid,
isVideoLicenceValid,
isVideoLanguageValid,
isVideoFileExtnameValid,
isVideoFileResolutionValid
} from '../videos'
+import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
+import { isVideoAuthorNameValid } from '../video-authors'
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
+const checkers: { [ id: string ]: (obj: any) => boolean } = {}
+checkers[ENDPOINT_ACTIONS.ADD_VIDEO] = checkAddVideo
+checkers[ENDPOINT_ACTIONS.UPDATE_VIDEO] = checkUpdateVideo
+checkers[ENDPOINT_ACTIONS.REMOVE_VIDEO] = checkRemoveVideo
+checkers[ENDPOINT_ACTIONS.REPORT_ABUSE] = checkReportVideo
+checkers[ENDPOINT_ACTIONS.ADD_CHANNEL] = checkAddVideoChannel
+checkers[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = checkUpdateVideoChannel
+checkers[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = checkRemoveVideoChannel
+checkers[ENDPOINT_ACTIONS.ADD_AUTHOR] = checkAddAuthor
+checkers[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = checkRemoveAuthor
+
function isEachRemoteRequestVideosValid (requests: any[]) {
return isArray(requests) &&
requests.every(request => {
if (!video) return false
- return (
- isRequestTypeAddValid(request.type) &&
- isCommonVideoAttributesValid(video) &&
- isVideoAuthorValid(video.author) &&
- isVideoThumbnailDataValid(video.thumbnailData)
- ) ||
- (
- isRequestTypeUpdateValid(request.type) &&
- isCommonVideoAttributesValid(video)
- ) ||
- (
- isRequestTypeRemoveValid(request.type) &&
- isVideoUUIDValid(video.uuid)
- ) ||
- (
- isRequestTypeReportAbuseValid(request.type) &&
- isVideoUUIDValid(request.data.videoUUID) &&
- isVideoAbuseReasonValid(request.data.reportReason) &&
- isVideoAbuseReporterUsernameValid(request.data.reporterUsername)
- )
+ const checker = checkers[request.type]
+ // We don't know the request type
+ if (checker === undefined) return false
+
+ return checker(video)
})
}
if (!video) return false
return (
- isVideoUUIDValid(video.uuid) &&
+ isUUIDValid(video.uuid) &&
(has(video, 'views') === false || isVideoViewsValid(video.views)) &&
(has(video, 'likes') === false || isVideoLikesValid(video.likes)) &&
(has(video, 'dislikes') === false || isVideoDislikesValid(video.dislikes))
if (!eventData) return false
return (
- isVideoUUIDValid(eventData.uuid) &&
+ isUUIDValid(eventData.uuid) &&
values(REQUEST_VIDEO_EVENT_TYPES).indexOf(eventData.eventType) !== -1 &&
isVideoEventCountValid(eventData.count)
)
// ---------------------------------------------------------------------------
function isCommonVideoAttributesValid (video: any) {
- return isVideoDateValid(video.createdAt) &&
- isVideoDateValid(video.updatedAt) &&
+ return isDateValid(video.createdAt) &&
+ isDateValid(video.updatedAt) &&
isVideoCategoryValid(video.category) &&
isVideoLicenceValid(video.licence) &&
isVideoLanguageValid(video.language) &&
isVideoDurationValid(video.duration) &&
isVideoNameValid(video.name) &&
isVideoTagsValid(video.tags) &&
- isVideoUUIDValid(video.uuid) &&
+ isUUIDValid(video.uuid) &&
isVideoViewsValid(video.views) &&
isVideoLikesValid(video.likes) &&
isVideoDislikesValid(video.dislikes) &&
})
}
-function isRequestTypeAddValid (value: string) {
- return value === ENDPOINT_ACTIONS.ADD
+function checkAddVideo (video: any) {
+ return isCommonVideoAttributesValid(video) &&
+ isUUIDValid(video.channelUUID) &&
+ isVideoThumbnailDataValid(video.thumbnailData)
+}
+
+function checkUpdateVideo (video: any) {
+ return isCommonVideoAttributesValid(video)
+}
+
+function checkRemoveVideo (video: any) {
+ return isUUIDValid(video.uuid)
+}
+
+function checkReportVideo (abuse: any) {
+ return isUUIDValid(abuse.videoUUID) &&
+ isVideoAbuseReasonValid(abuse.reportReason) &&
+ isVideoAbuseReporterUsernameValid(abuse.reporterUsername)
+}
+
+function checkAddVideoChannel (videoChannel: any) {
+ return isUUIDValid(videoChannel.uuid) &&
+ isVideoChannelNameValid(videoChannel.name) &&
+ isVideoChannelDescriptionValid(videoChannel.description) &&
+ isDateValid(videoChannel.createdAt) &&
+ isDateValid(videoChannel.updatedAt) &&
+ isUUIDValid(videoChannel.ownerUUID)
+}
+
+function checkUpdateVideoChannel (videoChannel: any) {
+ return isUUIDValid(videoChannel.uuid) &&
+ isVideoChannelNameValid(videoChannel.name) &&
+ isVideoChannelDescriptionValid(videoChannel.description) &&
+ isDateValid(videoChannel.createdAt) &&
+ isDateValid(videoChannel.updatedAt) &&
+ isUUIDValid(videoChannel.ownerUUID)
}
-function isRequestTypeUpdateValid (value: string) {
- return value === ENDPOINT_ACTIONS.UPDATE
+function checkRemoveVideoChannel (videoChannel: any) {
+ return isUUIDValid(videoChannel.uuid)
}
-function isRequestTypeRemoveValid (value: string) {
- return value === ENDPOINT_ACTIONS.REMOVE
+function checkAddAuthor (author: any) {
+ return isUUIDValid(author.uuid) &&
+ isVideoAuthorNameValid(author.name)
}
-function isRequestTypeReportAbuseValid (value: string) {
- return value === ENDPOINT_ACTIONS.REPORT_ABUSE
+function checkRemoveAuthor (author: any) {
+ return isUUIDValid(author.uuid)
}
--- /dev/null
+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
+}
--- /dev/null
+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
+}
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
const VIDEO_EVENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_EVENTS
-function isVideoIdOrUUIDValid (value: string) {
- return validator.isInt(value) || isVideoUUIDValid(value)
-}
-
-function isVideoAuthorValid (value: string) {
- return isUserUsernameValid(value)
-}
-
-function isVideoDateValid (value: string) {
- return exists(value) && validator.isISO8601(value)
-}
-
function isVideoCategoryValid (value: number) {
return VIDEO_CATEGORIES[value] !== undefined
}
return exists(value) && validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL_DATA)
}
-function isVideoUUIDValid (value: string) {
- return exists(value) && validator.isUUID('' + value, 4)
-}
-
function isVideoAbuseReasonValid (value: string) {
return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
}
// ---------------------------------------------------------------------------
export {
- isVideoIdOrUUIDValid,
- isVideoAuthorValid,
- isVideoDateValid,
isVideoCategoryValid,
isVideoLicenceValid,
isVideoLanguageValid,
isVideoThumbnailValid,
isVideoThumbnailDataValid,
isVideoFileExtnameValid,
- isVideoUUIDValid,
isVideoAbuseReasonValid,
isVideoAbuseReporterUsernameValid,
isVideoFile,
RequestEndpoint,
RequestVideoEventType,
RequestVideoQaduType,
+ RemoteVideoRequestType,
JobState
} from '../../shared/models'
PODS: [ 'id', 'host', 'score', 'createdAt' ],
USERS: [ 'id', 'username', 'createdAt' ],
VIDEO_ABUSES: [ 'id', 'createdAt' ],
+ VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ],
BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ]
}
VIDEO_ABUSES: {
REASON: { min: 2, max: 300 } // Length
},
+ VIDEO_CHANNELS: {
+ NAME: { min: 3, max: 50 }, // Length
+ DESCRIPTION: { min: 3, max: 250 } // Length
+ },
VIDEOS: {
NAME: { min: 3, max: 50 }, // Length
DESCRIPTION: { min: 3, max: 250 }, // Length
VIDEOS: 'videos'
}
-const REQUEST_ENDPOINT_ACTIONS: { [ id: string ]: any } = {}
+const REQUEST_ENDPOINT_ACTIONS: {
+ [ id: string ]: {
+ [ id: string ]: RemoteVideoRequestType
+ }
+} = {}
REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] = {
- ADD: 'add',
- UPDATE: 'update',
- REMOVE: 'remove',
+ ADD_VIDEO: 'add-video',
+ UPDATE_VIDEO: 'update-video',
+ REMOVE_VIDEO: 'remove-video',
+ ADD_CHANNEL: 'add-channel',
+ UPDATE_CHANNEL: 'update-channel',
+ REMOVE_CHANNEL: 'remove-channel',
+ ADD_AUTHOR: 'add-author',
+ REMOVE_AUTHOR: 'remove-author',
REPORT_ABUSE: 'report-abuse'
}
import { BlacklistedVideoModel } from './../models/video/video-blacklist-interface'
import { VideoFileModel } from './../models/video/video-file-interface'
import { VideoAbuseModel } from './../models/video/video-abuse-interface'
+import { VideoChannelModel } from './../models/video/video-channel-interface'
import { UserModel } from './../models/user/user-interface'
import { UserVideoRateModel } from './../models/user/user-video-rate-interface'
import { TagModel } from './../models/video/tag-interface'
UserVideoRate?: UserVideoRateModel,
User?: UserModel,
VideoAbuse?: VideoAbuseModel,
+ VideoChannel?: VideoChannelModel,
VideoFile?: VideoFileModel,
BlacklistedVideo?: BlacklistedVideoModel,
VideoTag?: VideoTagModel,
import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION, CACHE } from './constants'
import { clientsExist, usersExist } from './checker'
import { logger, createCertsIfNotExist, mkdirpPromise, rimrafPromise } from '../helpers'
+import { createUserAuthorAndChannel } from '../lib'
function installApplication () {
return db.sequelize.sync()
const username = 'root'
const role = USER_ROLES.ADMIN
const email = CONFIG.ADMIN.EMAIL
- const createOptions: { validate?: boolean } = {}
+ let validatePassword = true
let password = ''
// Do not generate a random password for tests
}
// Our password is weak so do not validate it
- createOptions.validate = false
+ validatePassword = false
} else {
password = passwordGenerator(8, true)
}
role,
videoQuota: -1
}
+ const user = db.User.build(userData)
- return db.User.create(userData, createOptions).then(createdUser => {
- logger.info('Username: ' + username)
- logger.info('User password: ' + password)
+ return createUserAuthorAndChannel(user, validatePassword)
+ .then(({ user }) => {
+ logger.info('Username: ' + username)
+ logger.info('User password: ' + password)
- logger.info('Creating Application table.')
- return db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION })
- })
+ logger.info('Creating Application table.')
+ return db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION })
+ })
})
}
}
private saveRemotePreviewAndReturnPath (video: VideoInstance) {
- const req = fetchRemotePreview(video.Author.Pod, video)
+ const req = fetchRemotePreview(video)
return new Promise<string>((res, rej) => {
const path = join(CACHE.DIRECTORIES.PREVIEWS, video.getPreviewName())
RemoteVideoRemoveData,
RemoteVideoReportAbuseData,
ResultList,
- Pod as FormattedPod
+ RemoteVideoRequestType,
+ Pod as FormattedPod,
+ RemoteVideoChannelCreateData,
+ RemoteVideoChannelUpdateData,
+ RemoteVideoChannelRemoveData,
+ RemoteVideoAuthorCreateData,
+ RemoteVideoAuthorRemoveData
} from '../../shared'
type QaduParam = { videoId: number, type: RequestVideoQaduType }
function addVideoToFriends (videoData: RemoteVideoCreateData, transaction: Sequelize.Transaction) {
const options = {
- type: ENDPOINT_ACTIONS.ADD,
+ type: ENDPOINT_ACTIONS.ADD_VIDEO,
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: videoData,
transaction
function updateVideoToFriends (videoData: RemoteVideoUpdateData, transaction: Sequelize.Transaction) {
const options = {
- type: ENDPOINT_ACTIONS.UPDATE,
+ type: ENDPOINT_ACTIONS.UPDATE_VIDEO,
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: videoData,
transaction
function removeVideoToFriends (videoParams: RemoteVideoRemoveData, transaction: Sequelize.Transaction) {
const options = {
- type: ENDPOINT_ACTIONS.REMOVE,
+ type: ENDPOINT_ACTIONS.REMOVE_VIDEO,
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: videoParams,
transaction
return createRequest(options)
}
+function addVideoAuthorToFriends (authorData: RemoteVideoAuthorCreateData, transaction: Sequelize.Transaction) {
+ const options = {
+ type: ENDPOINT_ACTIONS.ADD_AUTHOR,
+ endpoint: REQUEST_ENDPOINTS.VIDEOS,
+ data: authorData,
+ transaction
+ }
+ return createRequest(options)
+}
+
+function removeVideoAuthorToFriends (authorData: RemoteVideoAuthorRemoveData, transaction: Sequelize.Transaction) {
+ const options = {
+ type: ENDPOINT_ACTIONS.REMOVE_AUTHOR,
+ endpoint: REQUEST_ENDPOINTS.VIDEOS,
+ data: authorData,
+ transaction
+ }
+ return createRequest(options)
+}
+
+function addVideoChannelToFriends (videoChannelData: RemoteVideoChannelCreateData, transaction: Sequelize.Transaction) {
+ const options = {
+ type: ENDPOINT_ACTIONS.ADD_CHANNEL,
+ endpoint: REQUEST_ENDPOINTS.VIDEOS,
+ data: videoChannelData,
+ transaction
+ }
+ return createRequest(options)
+}
+
+function updateVideoChannelToFriends (videoChannelData: RemoteVideoChannelUpdateData, transaction: Sequelize.Transaction) {
+ const options = {
+ type: ENDPOINT_ACTIONS.UPDATE_CHANNEL,
+ endpoint: REQUEST_ENDPOINTS.VIDEOS,
+ data: videoChannelData,
+ transaction
+ }
+ return createRequest(options)
+}
+
+function removeVideoChannelToFriends (videoChannelParams: RemoteVideoChannelRemoveData, transaction: Sequelize.Transaction) {
+ const options = {
+ type: ENDPOINT_ACTIONS.REMOVE_CHANNEL,
+ endpoint: REQUEST_ENDPOINTS.VIDEOS,
+ data: videoChannelParams,
+ transaction
+ }
+ return createRequest(options)
+}
+
function reportAbuseVideoToFriend (reportData: RemoteVideoReportAbuseData, video: VideoInstance, transaction: Sequelize.Transaction) {
const options = {
type: ENDPOINT_ACTIONS.REPORT_ABUSE,
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: reportData,
- toIds: [ video.Author.podId ],
+ toIds: [ video.VideoChannel.Author.podId ],
transaction
}
return createRequest(options)
.finally(() => requestScheduler.activate())
}
+function sendOwnedDataToPod (podId: number) {
+ // First send authors
+ return sendOwnedAuthorsToPod(podId)
+ .then(() => sendOwnedChannelsToPod(podId))
+ .then(() => sendOwnedVideosToPod(podId))
+}
+
+function sendOwnedChannelsToPod (podId: number) {
+ return db.VideoChannel.listOwned()
+ .then(videoChannels => {
+ const tasks = []
+ videoChannels.forEach(videoChannel => {
+ const remoteVideoChannel = videoChannel.toAddRemoteJSON()
+ const options = {
+ type: 'add-channel' as 'add-channel',
+ endpoint: REQUEST_ENDPOINTS.VIDEOS,
+ data: remoteVideoChannel,
+ toIds: [ podId ],
+ transaction: null
+ }
+
+ const p = createRequest(options)
+ tasks.push(p)
+ })
+
+ return Promise.all(tasks)
+ })
+}
+
+function sendOwnedAuthorsToPod (podId: number) {
+ return db.Author.listOwned()
+ .then(authors => {
+ const tasks = []
+ authors.forEach(author => {
+ const remoteAuthor = author.toAddRemoteJSON()
+ const options = {
+ type: 'add-author' as 'add-author',
+ endpoint: REQUEST_ENDPOINTS.VIDEOS,
+ data: remoteAuthor,
+ toIds: [ podId ],
+ transaction: null
+ }
+
+ const p = createRequest(options)
+ tasks.push(p)
+ })
+
+ return Promise.all(tasks)
+ })
+}
+
function sendOwnedVideosToPod (podId: number) {
- db.Video.listOwnedAndPopulateAuthorAndTags()
+ return db.Video.listOwnedAndPopulateAuthorAndTags()
.then(videosList => {
const tasks = []
videosList.forEach(video => {
const promise = video.toAddRemoteJSON()
.then(remoteVideo => {
const options = {
- type: 'add',
+ type: 'add-video' as 'add-video',
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: remoteVideo,
toIds: [ podId ],
})
}
-function fetchRemotePreview (pod: PodInstance, video: VideoInstance) {
- const host = video.Author.Pod.host
+function fetchRemotePreview (video: VideoInstance) {
+ const host = video.VideoChannel.Author.Pod.host
const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName())
return request.get(REMOTE_SCHEME.HTTP + '://' + host + path)
export {
activateSchedulers,
addVideoToFriends,
+ removeVideoAuthorToFriends,
updateVideoToFriends,
+ addVideoAuthorToFriends,
reportAbuseVideoToFriend,
quickAndDirtyUpdateVideoToFriends,
quickAndDirtyUpdatesVideoToFriends,
quitFriends,
removeFriend,
removeVideoToFriends,
- sendOwnedVideosToPod,
+ sendOwnedDataToPod,
getRequestScheduler,
getRequestVideoQaduScheduler,
getRequestVideoEventScheduler,
- fetchRemotePreview
+ fetchRemotePreview,
+ addVideoChannelToFriends,
+ updateVideoChannelToFriends,
+ removeVideoChannelToFriends
}
// ---------------------------------------------------------------------------
.then(podCreated => {
// Add our videos to the request scheduler
- sendOwnedVideosToPod(podCreated.id)
+ sendOwnedDataToPod(podCreated.id)
})
.catch(err => {
logger.error('Cannot add friend %s pod.', pod.host, err)
// Wrapper that populate "toIds" argument with all our friends if it is not specified
type CreateRequestOptions = {
- type: string
+ type: RemoteVideoRequestType
endpoint: RequestEndpoint
data: Object
toIds?: number[]
export * from './request'
export * from './friends'
export * from './oauth-model'
+export * from './user'
+export * from './video-channel'
--- /dev/null
+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
+}
--- /dev/null
+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
+}
return next()
}
+function setVideoChannelsSort (req: express.Request, res: express.Response, next: express.NextFunction) {
+ if (!req.query.sort) req.query.sort = '-createdAt'
+
+ return next()
+}
+
function setVideosSort (req: express.Request, res: express.Response, next: express.NextFunction) {
if (!req.query.sort) req.query.sort = '-createdAt'
setPodsSort,
setUsersSort,
setVideoAbusesSort,
+ setVideoChannelsSort,
setVideosSort,
setBlacklistSort
}
export * from './users'
export * from './videos'
export * from './video-blacklist'
+export * from './video-channels'
import { checkErrors } from './utils'
import { CONFIG } from '../../initializers'
-import { logger } from '../../helpers'
-import { checkVideoExists, isVideoIdOrUUIDValid } from '../../helpers/custom-validators/videos'
-import { isTestInstance } from '../../helpers/core-utils'
+import {
+ logger,
+ isTestInstance,
+ checkVideoExists,
+ isIdOrUUIDValid
+} from '../../helpers'
const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/'
const videoWatchRegex = new RegExp('([^/]+)$')
}
const videoId = matches[1]
- if (isVideoIdOrUUIDValid(videoId) === false) {
+ if (isIdOrUUIDValid(videoId) === false) {
return res.status(400)
.json({ error: 'Invalid video id.' })
.end()
const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
+const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS)
const podsSortValidator = checkSort(SORTABLE_PODS_COLUMNS)
const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
+const videoChannelsSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_COLUMNS)
// ---------------------------------------------------------------------------
podsSortValidator,
usersSortValidator,
videoAbusesSortValidator,
+ videoChannelsSortValidator,
videosSortValidator,
blacklistSortValidator
}
isUserPasswordValid,
isUserVideoQuotaValid,
isUserDisplayNSFWValid,
- isVideoIdOrUUIDValid
+ isIdOrUUIDValid
} from '../../helpers'
import { UserInstance, VideoInstance } from '../../models'
]
const usersVideoRatingValidator = [
- param('videoId').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
+ param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
import { database as db } from '../../initializers/database'
import { checkErrors } from './utils'
-import { logger, isVideoIdOrUUIDValid, checkVideoExists } from '../../helpers'
+import { logger, isIdOrUUIDValid, checkVideoExists } from '../../helpers'
const videosBlacklistRemoveValidator = [
- param('videoId').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
+ param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
]
const videosBlacklistAddValidator = [
- param('videoId').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
+ param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosBlacklist parameters', { parameters: req.params })
--- /dev/null
+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()
+ })
+}
isVideoLanguageValid,
isVideoTagsValid,
isVideoNSFWValid,
- isVideoIdOrUUIDValid,
+ isIdOrUUIDValid,
isVideoAbuseReasonValid,
isVideoRatingTypeValid,
getDurationFromVideoFile,
- checkVideoExists
+ checkVideoExists,
+ isIdValid
} from '../../helpers'
const videosAddValidator = [
body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'),
body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
+ body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'),
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
const videoFile: Express.Multer.File = req.files['videofile'][0]
const user = res.locals.oauth.token.User
- user.isAbleToUploadVideo(videoFile)
+ return db.VideoChannel.loadByIdAndAuthor(req.body.channelId, user.Author.id)
+ .then(videoChannel => {
+ if (!videoChannel) {
+ res.status(400)
+ .json({ error: 'Unknown video video channel for this author.' })
+ .end()
+
+ return undefined
+ }
+
+ res.locals.videoChannel = videoChannel
+
+ return user.isAbleToUploadVideo(videoFile)
+ })
.then(isAble => {
if (isAble === false) {
res.status(403)
]
const videosUpdateValidator = [
- param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+ param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'),
body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
.end()
}
- if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
+ if (res.locals.video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) {
return res.status(403)
.json({ error: 'Cannot update video of another user' })
.end()
]
const videosGetValidator = [
- param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+ param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosGet parameters', { parameters: req.params })
]
const videosRemoveValidator = [
- param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+ param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosRemove parameters', { parameters: req.params })
]
const videoAbuseReportValidator = [
- param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+ param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
]
const videoRateValidator = [
- param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+ param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
where: {
accessToken: bearerToken
},
- include: [ OAuthToken['sequelize'].models.User ]
+ include: [
+ {
+ model: OAuthToken['sequelize'].models.User,
+ include: [
+ {
+ model: OAuthToken['sequelize'].models.Author,
+ required: true
+ }
+ ]
+ }
+ ]
}
return OAuthToken.findOne(query).then(token => {
where: {
refreshToken: refreshToken
},
- include: [ OAuthToken['sequelize'].models.User ]
+ include: [
+ {
+ model: OAuthToken['sequelize'].models.User,
+ include: [
+ {
+ model: OAuthToken['sequelize'].models.Author,
+ required: true
+ }
+ ]
+ }
+ ]
}
return OAuthToken.findOne(query).then(token => {
const Pod = db.Pod
// We make a join between videos and authors to find the podId of our video event requests
- const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' +
+ const podJoins = 'INNER JOIN "VideoChannels" ON "VideoChannels"."authorId" = "Authors"."id" ' +
+ 'INNER JOIN "Videos" ON "Videos"."channelId" = "VideoChannels"."id" ' +
'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"'
return Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins).then(podIds => {
const eventsGrouped: RequestsVideoEventGrouped = {}
events.forEach(event => {
- const pod = event.Video.Author.Pod
+ const pod = event.Video.VideoChannel.Author.Pod
if (!eventsGrouped[pod.id]) eventsGrouped[pod.id] = []
import { User as FormattedUser } from '../../../shared/models/users/user.model'
import { UserRole } from '../../../shared/models/users/user-role.type'
import { ResultList } from '../../../shared/models/result-list.model'
+import { AuthorInstance } from '../video/author-interface'
export namespace UserMethods {
export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean>
export type GetByUsername = (username: string) => Promise<UserInstance>
- export type List = () => Promise<UserInstance[]>
-
export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<UserInstance> >
export type LoadById = (id: number) => Promise<UserInstance>
export type LoadByUsername = (username: string) => Promise<UserInstance>
+ export type LoadByUsernameAndPopulateChannels = (username: string) => Promise<UserInstance>
export type LoadByUsernameOrEmail = (username: string, email: string) => Promise<UserInstance>
}
countTotal: UserMethods.CountTotal,
getByUsername: UserMethods.GetByUsername,
- list: UserMethods.List,
listForApi: UserMethods.ListForApi,
loadById: UserMethods.LoadById,
loadByUsername: UserMethods.LoadByUsername,
+ loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels,
loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
}
displayNSFW?: boolean
role: UserRole
videoQuota: number
+
+ Author?: AuthorInstance
}
export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> {
let isAdmin: UserMethods.IsAdmin
let countTotal: UserMethods.CountTotal
let getByUsername: UserMethods.GetByUsername
-let list: UserMethods.List
let listForApi: UserMethods.ListForApi
let loadById: UserMethods.LoadById
let loadByUsername: UserMethods.LoadByUsername
+let loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels
let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo
countTotal,
getByUsername,
- list,
listForApi,
loadById,
loadByUsername,
+ loadByUsernameAndPopulateChannels,
loadByUsernameOrEmail
]
const instanceMethods = [
}
toFormattedJSON = function (this: UserInstance) {
- return {
+ const json = {
id: this.id,
username: this.username,
email: this.email,
displayNSFW: this.displayNSFW,
role: this.role,
videoQuota: this.videoQuota,
- createdAt: this.createdAt
+ createdAt: this.createdAt,
+ author: {
+ id: this.Author.id,
+ uuid: this.Author.uuid
+ }
}
+
+ if (Array.isArray(this.Author.VideoChannels) === true) {
+ const videoChannels = this.Author.VideoChannels
+ .map(c => c.toFormattedJSON())
+ .sort((v1, v2) => {
+ if (v1.createdAt < v2.createdAt) return -1
+ if (v1.createdAt === v2.createdAt) return 0
+
+ return 1
+ })
+
+ json['videoChannels'] = videoChannels
+ }
+
+ return json
}
isAdmin = function (this: UserInstance) {
const query = {
where: {
username: username
- }
+ },
+ include: [ { model: User['sequelize'].models.Author, required: true } ]
}
return User.findOne(query)
}
-list = function () {
- return User.findAll()
-}
-
listForApi = function (start: number, count: number, sort: string) {
const query = {
offset: start,
limit: count,
- order: [ getSort(sort) ]
+ order: [ getSort(sort) ],
+ include: [ { model: User['sequelize'].models.Author, required: true } ]
}
return User.findAndCountAll(query).then(({ rows, count }) => {
}
loadById = function (id: number) {
- return User.findById(id)
+ const options = {
+ include: [ { model: User['sequelize'].models.Author, required: true } ]
+ }
+
+ return User.findById(id, options)
}
loadByUsername = function (username: string) {
const query = {
where: {
username
- }
+ },
+ include: [ { model: User['sequelize'].models.Author, required: true } ]
+ }
+
+ return User.findOne(query)
+}
+
+loadByUsernameAndPopulateChannels = function (username: string) {
+ const query = {
+ where: {
+ username
+ },
+ include: [
+ {
+ model: User['sequelize'].models.Author,
+ required: true,
+ include: [ User['sequelize'].models.VideoChannel ]
+ }
+ ]
}
return User.findOne(query)
loadByUsernameOrEmail = function (username: string, email: string) {
const query = {
+ include: [ { model: User['sequelize'].models.Author, required: true } ],
where: {
$or: [ { username }, { email } ]
}
// ---------------------------------------------------------------------------
function getOriginalVideoFileTotalFromUser (user: UserInstance) {
- // Don't use sequelize because we need to use a subquery
+ // Don't use sequelize because we need to use a sub query
const query = 'SELECT SUM("size") AS "total" FROM ' +
'(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
- 'INNER JOIN "Authors" ON "Videos"."authorId" = "Authors"."id" ' +
+ 'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' +
+ 'INNER JOIN "Authors" ON "VideoChannels"."authorId" = "Authors"."id" ' +
'INNER JOIN "Users" ON "Authors"."userId" = "Users"."id" ' +
'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
import * as Promise from 'bluebird'
import { PodInstance } from '../pod/pod-interface'
+import { RemoteVideoAuthorCreateData } from '../../../shared/models/pods/remote-video/remote-video-author-create-request.model'
+import { VideoChannelInstance } from './video-channel-interface'
export namespace AuthorMethods {
- export type FindOrCreateAuthor = (
- name: string,
- podId: number,
- userId: number,
- transaction: Sequelize.Transaction
- ) => Promise<AuthorInstance>
+ export type Load = (id: number) => Promise<AuthorInstance>
+ export type LoadByUUID = (uuid: string) => Promise<AuthorInstance>
+ export type LoadAuthorByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Promise<AuthorInstance>
+ export type ListOwned = () => Promise<AuthorInstance[]>
+
+ export type ToAddRemoteJSON = (this: AuthorInstance) => RemoteVideoAuthorCreateData
+ export type IsOwned = (this: AuthorInstance) => boolean
}
export interface AuthorClass {
- findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor
+ loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID
+ load: AuthorMethods.Load
+ loadByUUID: AuthorMethods.LoadByUUID
+ listOwned: AuthorMethods.ListOwned
}
export interface AuthorAttributes {
name: string
+ uuid?: string
+
+ podId?: number
+ userId?: number
}
export interface AuthorInstance extends AuthorClass, AuthorAttributes, Sequelize.Instance<AuthorAttributes> {
+ isOwned: AuthorMethods.IsOwned
+ toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON
+
id: number
createdAt: Date
updatedAt: Date
- podId: number
Pod: PodInstance
+ VideoChannels: VideoChannelInstance[]
}
export interface AuthorModel extends AuthorClass, Sequelize.Model<AuthorInstance, AuthorAttributes> {}
import * as Sequelize from 'sequelize'
import { isUserUsernameValid } from '../../helpers'
+import { removeVideoAuthorToFriends } from '../../lib'
import { addMethodsToModel } from '../utils'
import {
} from './author-interface'
let Author: Sequelize.Model<AuthorInstance, AuthorAttributes>
-let findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor
+let loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID
+let load: AuthorMethods.Load
+let loadByUUID: AuthorMethods.LoadByUUID
+let listOwned: AuthorMethods.ListOwned
+let isOwned: AuthorMethods.IsOwned
+let toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON
export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
Author = sequelize.define<AuthorInstance, AuthorAttributes>('Author',
{
+ uuid: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ allowNull: false,
+ validate: {
+ isUUID: 4
+ }
+ },
name: {
type: DataTypes.STRING,
allowNull: false,
fields: [ 'name', 'podId' ],
unique: true
}
- ]
+ ],
+ hooks: { afterDestroy }
}
)
- const classMethods = [ associate, findOrCreateAuthor ]
- addMethodsToModel(Author, classMethods)
+ const classMethods = [
+ associate,
+ loadAuthorByPodAndUUID,
+ load,
+ loadByUUID,
+ listOwned
+ ]
+ const instanceMethods = [
+ isOwned,
+ toAddRemoteJSON
+ ]
+ addMethodsToModel(Author, classMethods, instanceMethods)
return Author
}
onDelete: 'cascade'
})
- Author.hasMany(models.Video, {
+ Author.hasMany(models.VideoChannel, {
foreignKey: {
name: 'authorId',
allowNull: false
},
- onDelete: 'cascade'
+ onDelete: 'cascade',
+ hooks: true
})
}
-findOrCreateAuthor = function (name: string, podId: number, userId: number, transaction: Sequelize.Transaction) {
- const author = {
- name,
- podId,
- userId
+function afterDestroy (author: AuthorInstance, options: { transaction: Sequelize.Transaction }) {
+ if (author.isOwned()) {
+ const removeVideoAuthorToFriendsParams = {
+ uuid: author.uuid
+ }
+
+ return removeVideoAuthorToFriends(removeVideoAuthorToFriendsParams, options.transaction)
+ }
+
+ return undefined
+}
+
+toAddRemoteJSON = function (this: AuthorInstance) {
+ const json = {
+ uuid: this.uuid,
+ name: this.name
+ }
+
+ return json
+}
+
+isOwned = function (this: AuthorInstance) {
+ return this.podId === null
+}
+
+// ------------------------------ STATICS ------------------------------
+
+listOwned = function () {
+ const query: Sequelize.FindOptions<AuthorAttributes> = {
+ where: {
+ podId: null
+ }
+ }
+
+ return Author.findAll(query)
+}
+
+load = function (id: number) {
+ return Author.findById(id)
+}
+
+loadByUUID = function (uuid: string) {
+ const query: Sequelize.FindOptions<AuthorAttributes> = {
+ where: {
+ uuid
+ }
}
- const query: Sequelize.FindOrInitializeOptions<AuthorAttributes> = {
- where: author,
- defaults: author,
+ return Author.findOne(query)
+}
+
+loadAuthorByPodAndUUID = function (uuid: string, podId: number, transaction: Sequelize.Transaction) {
+ const query: Sequelize.FindOptions<AuthorAttributes> = {
+ where: {
+ podId,
+ uuid
+ },
transaction
}
- return Author.findOrCreate(query).then(([ authorInstance ]) => authorInstance)
+ return Author.find(query)
}
export * from './tag-interface'
export * from './video-abuse-interface'
export * from './video-blacklist-interface'
+export * from './video-channel-interface'
export * from './video-tag-interface'
export * from './video-file-interface'
export * from './video-interface'
--- /dev/null
+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> {}
--- /dev/null
+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)
+}
import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
// Don't use barrel, import just what we need
-import { Video as FormattedVideo } from '../../../shared/models/videos/video.model'
+import {
+ Video as FormattedVideo,
+ VideoDetails as FormattedDetailsVideo
+} from '../../../shared/models/videos/video.model'
import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model'
import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model'
import { ResultList } from '../../../shared/models/result-list.model'
+import { VideoChannelInstance } from './video-channel-interface'
export namespace VideoMethods {
export type GetThumbnailName = (this: VideoInstance) => string
export type GetPreviewName = (this: VideoInstance) => string
export type IsOwned = (this: VideoInstance) => boolean
export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo
+ export type ToFormattedDetailsJSON = (this: VideoInstance) => FormattedDetailsVideo
export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance
export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string
) => Promise< ResultList<VideoInstance> >
export type Load = (id: number) => Promise<VideoInstance>
- export type LoadByUUID = (uuid: string) => Promise<VideoInstance>
- export type LoadByHostAndUUID = (fromHost: string, uuid: string) => Promise<VideoInstance>
+ export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance>
+ export type LoadByHostAndUUID = (fromHost: string, uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance>
export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance>
export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance>
export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance>
dislikes?: number
remote: boolean
- Author?: AuthorInstance
+ channelId?: number
+
+ VideoChannel?: VideoChannelInstance
Tags?: TagInstance[]
VideoFiles?: VideoFileInstance[]
}
removeTorrent: VideoMethods.RemoveTorrent
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
toFormattedJSON: VideoMethods.ToFormattedJSON
+ toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
let getTorrentFileName: VideoMethods.GetTorrentFileName
let isOwned: VideoMethods.IsOwned
let toFormattedJSON: VideoMethods.ToFormattedJSON
+let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON
let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
},
{
indexes: [
- {
- fields: [ 'authorId' ]
- },
{
fields: [ 'name' ]
},
},
{
fields: [ 'uuid' ]
+ },
+ {
+ fields: [ 'channelId' ]
}
],
hooks: {
removeTorrent,
toAddRemoteJSON,
toFormattedJSON,
+ toFormattedDetailsJSON,
toUpdateRemoteJSON,
optimizeOriginalVideofile,
transcodeOriginalVideofile,
// ------------------------------ METHODS ------------------------------
function associate (models) {
- Video.belongsTo(models.Author, {
+ Video.belongsTo(models.VideoChannel, {
foreignKey: {
- name: 'authorId',
+ name: 'channelId',
allowNull: false
},
onDelete: 'cascade'
toFormattedJSON = function (this: VideoInstance) {
let podHost
- if (this.Author.Pod) {
- podHost = this.Author.Pod.host
+ if (this.VideoChannel.Author.Pod) {
+ podHost = this.VideoChannel.Author.Pod.host
} else {
// It means it's our video
podHost = CONFIG.WEBSERVER.HOST
description: this.description,
podHost,
isLocal: this.isOwned(),
- author: this.Author.name,
+ author: this.VideoChannel.Author.name,
+ duration: this.duration,
+ views: this.views,
+ likes: this.likes,
+ dislikes: this.dislikes,
+ tags: map<TagInstance, string>(this.Tags, 'name'),
+ thumbnailPath: this.getThumbnailPath(),
+ previewPath: this.getPreviewPath(),
+ embedPath: this.getEmbedPath(),
+ createdAt: this.createdAt,
+ updatedAt: this.updatedAt
+ }
+
+ return json
+}
+
+toFormattedDetailsJSON = function (this: VideoInstance) {
+ let podHost
+
+ if (this.VideoChannel.Author.Pod) {
+ podHost = this.VideoChannel.Author.Pod.host
+ } else {
+ // It means it's our video
+ podHost = CONFIG.WEBSERVER.HOST
+ }
+
+ // Maybe our pod is not up to date and there are new categories since our version
+ let categoryLabel = VIDEO_CATEGORIES[this.category]
+ if (!categoryLabel) categoryLabel = 'Misc'
+
+ // Maybe our pod is not up to date and there are new licences since our version
+ let licenceLabel = VIDEO_LICENCES[this.licence]
+ if (!licenceLabel) licenceLabel = 'Unknown'
+
+ // Language is an optional attribute
+ let languageLabel = VIDEO_LANGUAGES[this.language]
+ if (!languageLabel) languageLabel = 'Unknown'
+
+ const json = {
+ id: this.id,
+ uuid: this.uuid,
+ name: this.name,
+ category: this.category,
+ categoryLabel,
+ licence: this.licence,
+ licenceLabel,
+ language: this.language,
+ languageLabel,
+ nsfw: this.nsfw,
+ description: this.description,
+ podHost,
+ isLocal: this.isOwned(),
+ author: this.VideoChannel.Author.name,
duration: this.duration,
views: this.views,
likes: this.likes,
embedPath: this.getEmbedPath(),
createdAt: this.createdAt,
updatedAt: this.updatedAt,
+ channel: this.VideoChannel.toFormattedJSON(),
files: []
}
language: this.language,
nsfw: this.nsfw,
description: this.description,
- author: this.Author.name,
+ channelUUID: this.VideoChannel.uuid,
duration: this.duration,
thumbnailData: thumbnailData.toString('binary'),
tags: map<TagInstance, string>(this.Tags, 'name'),
language: this.language,
nsfw: this.nsfw,
description: this.description,
- author: this.Author.name,
duration: this.duration,
tags: map<TagInstance, string>(this.Tags, 'name'),
createdAt: this.createdAt,
order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
include: [
{
- model: Video['sequelize'].models.Author,
- include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+ model: Video['sequelize'].models.VideoChannel,
+ include: [
+ {
+ model: Video['sequelize'].models.Author,
+ include: [
+ {
+ model: Video['sequelize'].models.Pod,
+ required: false
+ }
+ ]
+ }
+ ]
},
Video['sequelize'].models.Tag,
Video['sequelize'].models.VideoFile
})
}
-loadByHostAndUUID = function (fromHost: string, uuid: string) {
- const query = {
+loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
+ const query: Sequelize.FindOptions<VideoAttributes> = {
where: {
uuid
},
model: Video['sequelize'].models.VideoFile
},
{
- model: Video['sequelize'].models.Author,
+ model: Video['sequelize'].models.VideoChannel,
include: [
{
- model: Video['sequelize'].models.Pod,
- required: true,
- where: {
- host: fromHost
- }
+ model: Video['sequelize'].models.Author,
+ include: [
+ {
+ model: Video['sequelize'].models.Pod,
+ required: true,
+ where: {
+ host: fromHost
+ }
+ }
+ ]
}
]
}
]
}
+ if (t !== undefined) query.transaction = t
+
return Video.findOne(query)
}
},
include: [
Video['sequelize'].models.VideoFile,
- Video['sequelize'].models.Author,
+ {
+ model: Video['sequelize'].models.VideoChannel,
+ include: [ Video['sequelize'].models.Author ]
+ },
Video['sequelize'].models.Tag
]
}
model: Video['sequelize'].models.VideoFile
},
{
- model: Video['sequelize'].models.Author,
- where: {
- name: author
- }
+ model: Video['sequelize'].models.VideoChannel,
+ include: [
+ {
+ model: Video['sequelize'].models.Author,
+ where: {
+ name: author
+ }
+ }
+ ]
}
]
}
return Video.findById(id)
}
-loadByUUID = function (uuid: string) {
- const query = {
+loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
+ const query: Sequelize.FindOptions<VideoAttributes> = {
where: {
uuid
},
include: [ Video['sequelize'].models.VideoFile ]
}
+
+ if (t !== undefined) query.transaction = t
+
return Video.findOne(query)
}
loadAndPopulateAuthor = function (id: number) {
const options = {
- include: [ Video['sequelize'].models.VideoFile, Video['sequelize'].models.Author ]
+ include: [
+ Video['sequelize'].models.VideoFile,
+ {
+ model: Video['sequelize'].models.VideoChannel,
+ include: [ Video['sequelize'].models.Author ]
+ }
+ ]
}
return Video.findById(id, options)
const options = {
include: [
{
- model: Video['sequelize'].models.Author,
- include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+ model: Video['sequelize'].models.VideoChannel,
+ include: [
+ {
+ model: Video['sequelize'].models.Author,
+ include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+ }
+ ]
},
Video['sequelize'].models.Tag,
Video['sequelize'].models.VideoFile
},
include: [
{
- model: Video['sequelize'].models.Author,
- include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+ model: Video['sequelize'].models.VideoChannel,
+ include: [
+ {
+ model: Video['sequelize'].models.Author,
+ include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+ }
+ ]
},
Video['sequelize'].models.Tag,
Video['sequelize'].models.VideoFile
const authorInclude: Sequelize.IncludeOptions = {
model: Video['sequelize'].models.Author,
- include: [
- podInclude
- ]
+ include: [ podInclude ]
+ }
+
+ const videoChannelInclude: Sequelize.IncludeOptions = {
+ model: Video['sequelize'].models.VideoChannel,
+ include: [ authorInclude ],
+ required: true
}
const tagInclude: Sequelize.IncludeOptions = {
$iLike: '%' + value + '%'
}
}
-
- // authorInclude.or = true
} else {
query.where[field] = {
$iLike: '%' + value + '%'
}
query.include = [
- authorInclude, tagInclude, videoFileInclude
+ videoChannelInclude, tagInclude, videoFileInclude
]
return Video.findAndCountAll(query).then(({ rows, count }) => {
baseUrlHttp = CONFIG.WEBSERVER.URL
baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
} else {
- baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host
- baseUrlWs = REMOTE_SCHEME.WS + '://' + video.Author.Pod.host
+ baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Author.Pod.host
+ baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Author.Pod.host
}
return { baseUrlHttp, baseUrlWs }
export * from './remote-qadu-video-request.model'
+export * from './remote-video-author-create-request.model'
+export * from './remote-video-author-remove-request.model'
export * from './remote-video-event-request.model'
export * from './remote-video-request.model'
export * from './remote-video-create-request.model'
export * from './remote-video-update-request.model'
export * from './remote-video-remove-request.model'
+export * from './remote-video-channel-create-request.model'
+export * from './remote-video-channel-update-request.model'
+export * from './remote-video-channel-remove-request.model'
export * from './remote-video-report-abuse-request.model'
--- /dev/null
+import { RemoteVideoRequest } from './remote-video-request.model'
+
+export interface RemoteVideoAuthorCreateData {
+ uuid: string
+ name: string
+}
+
+export interface RemoteVideoAuthorCreateRequest extends RemoteVideoRequest {
+ type: 'add-author'
+ data: RemoteVideoAuthorCreateData
+}
--- /dev/null
+import { RemoteVideoRequest } from './remote-video-request.model'
+
+export interface RemoteVideoAuthorRemoveData {
+ uuid: string
+}
+
+export interface RemoteVideoAuthorRemoveRequest extends RemoteVideoRequest {
+ type: 'remove-author'
+ data: RemoteVideoAuthorRemoveData
+}
--- /dev/null
+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
+}
--- /dev/null
+import { RemoteVideoRequest } from './remote-video-request.model'
+
+export interface RemoteVideoChannelRemoveData {
+ uuid: string
+}
+
+export interface RemoteVideoChannelRemoveRequest extends RemoteVideoRequest {
+ type: 'remove-channel'
+ data: RemoteVideoChannelRemoveData
+}
--- /dev/null
+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
+}
export interface RemoteVideoCreateData {
uuid: string
- author: string
+ channelUUID: string
tags: string[]
name: string
category: number
}
export interface RemoteVideoCreateRequest extends RemoteVideoRequest {
- type: 'add'
+ type: 'add-video'
data: RemoteVideoCreateData
}
}
export interface RemoteVideoRemoveRequest extends RemoteVideoRequest {
- type: 'remove'
+ type: 'remove-video'
data: RemoteVideoRemoveData
}
export interface RemoteVideoRequest {
- type: 'add' | 'update' | 'remove' | 'report-abuse'
+ type: RemoteVideoRequestType
data: any
}
+
+export type RemoteVideoRequestType = 'add-video' | 'update-video' | 'remove-video' |
+ 'add-channel' | 'update-channel' | 'remove-channel' |
+ 'report-abuse' |
+ 'add-author' | 'remove-author'
+import { RemoteVideoRequest } from './remote-video-request.model'
+
export interface RemoteVideoUpdateData {
uuid: string
tags: string[]
}[]
}
-export interface RemoteVideoUpdateRequest {
- type: 'update'
+export interface RemoteVideoUpdateRequest extends RemoteVideoRequest {
+ type: 'update-video'
data: RemoteVideoUpdateData
}
import { UserRole } from './user-role.type'
+import { VideoChannel } from '../videos/video-channel.model'
export interface User {
id: number
displayNSFW: boolean
role: UserRole
videoQuota: number
- createdAt: Date
+ createdAt: Date,
+ author: {
+ id: number
+ uuid: string
+ }
+ videoChannels?: VideoChannel[]
}
export * from './video-abuse-create.model'
export * from './video-abuse.model'
export * from './video-blacklist.model'
+export * from './video-channel-create.model'
+export * from './video-channel-update.model'
+export * from './video-channel.model'
export * from './video-create.model'
export * from './video-rate.type'
export * from './video-resolution.enum'
--- /dev/null
+export interface VideoChannelCreate {
+ name: string
+ description?: string
+}
--- /dev/null
+export interface VideoChannelUpdate {
+ name: string
+ description: string
+}
--- /dev/null
+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[]
+}
licence: number
language: number
description: string
+ channelId: number
nsfw: boolean
name: string
tags: string[]
+import { VideoChannel } from './video-channel.model'
+
export interface VideoFile {
magnetUri: string
resolution: number
likes: number
dislikes: number
nsfw: boolean
+}
+
+export interface VideoDetails extends Video {
+ channel: VideoChannel
files: VideoFile[]
}