Remove references to author
This commit is contained in:
parent
0d0e8dd090
commit
38fa206583
|
@ -32,8 +32,8 @@
|
||||||
// RemoteVideoChannelCreateData,
|
// RemoteVideoChannelCreateData,
|
||||||
// RemoteVideoChannelUpdateData,
|
// RemoteVideoChannelUpdateData,
|
||||||
// RemoteVideoChannelRemoveData,
|
// RemoteVideoChannelRemoveData,
|
||||||
// RemoteVideoAuthorRemoveData,
|
// RemoteVideoAccountRemoveData,
|
||||||
// RemoteVideoAuthorCreateData
|
// RemoteVideoAccountCreateData
|
||||||
// } from '../../../../shared'
|
// } from '../../../../shared'
|
||||||
// import { VideoInstance } from '../../../models/video/video-interface'
|
// import { VideoInstance } from '../../../models/video/video-interface'
|
||||||
//
|
//
|
||||||
|
@ -49,8 +49,8 @@
|
||||||
// functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper
|
// functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper
|
||||||
// functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper
|
// functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper
|
||||||
// functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper
|
// functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper
|
||||||
// functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper
|
// functionsHash[ENDPOINT_ACTIONS.ADD_ACCOUNT] = addRemoteVideoAccountRetryWrapper
|
||||||
// functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper
|
// functionsHash[ENDPOINT_ACTIONS.REMOVE_ACCOUNT] = removeRemoteVideoAccountRetryWrapper
|
||||||
//
|
//
|
||||||
// const remoteVideosRouter = express.Router()
|
// const remoteVideosRouter = express.Router()
|
||||||
//
|
//
|
||||||
|
@ -245,24 +245,24 @@
|
||||||
// logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
|
// logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// async function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
|
// async function removeRemoteVideoAccountRetryWrapper (accountAttributesToRemove: RemoteVideoAccountRemoveData, fromPod: PodInstance) {
|
||||||
// const options = {
|
// const options = {
|
||||||
// arguments: [ authorAttributesToRemove, fromPod ],
|
// arguments: [ accountAttributesToRemove, fromPod ],
|
||||||
// errorMessage: 'Cannot remove the remote video author with many retries.'
|
// errorMessage: 'Cannot remove the remote video account with many retries.'
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// await retryTransactionWrapper(removeRemoteVideoAuthor, options)
|
// await retryTransactionWrapper(removeRemoteVideoAccount, options)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// async function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
|
// async function removeRemoteVideoAccount (accountAttributesToRemove: RemoteVideoAccountRemoveData, fromPod: PodInstance) {
|
||||||
// logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
|
// logger.debug('Removing remote video account "%s".', accountAttributesToRemove.uuid)
|
||||||
//
|
//
|
||||||
// await db.sequelize.transaction(async t => {
|
// await db.sequelize.transaction(async t => {
|
||||||
// const videoAuthor = await db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
|
// const videoAccount = await db.Account.loadAccountByPodAndUUID(accountAttributesToRemove.uuid, fromPod.id, t)
|
||||||
// await videoAuthor.destroy({ transaction: t })
|
// await videoAccount.destroy({ transaction: t })
|
||||||
// })
|
// })
|
||||||
//
|
//
|
||||||
// logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid)
|
// logger.info('Remote video account with uuid %s removed.', accountAttributesToRemove.uuid)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
|
// async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {
|
||||||
UserRole,
|
UserRole,
|
||||||
UserRight
|
UserRight
|
||||||
} from '../../../shared'
|
} from '../../../shared'
|
||||||
import { createUserAuthorAndChannel } from '../../lib'
|
import { createUserAccountAndChannel } from '../../lib'
|
||||||
import { UserInstance } from '../../models'
|
import { UserInstance } from '../../models'
|
||||||
import { videosSortValidator } from '../../middlewares/validators/sort'
|
import { videosSortValidator } from '../../middlewares/validators/sort'
|
||||||
import { setVideosSort } from '../../middlewares/sort'
|
import { setVideosSort } from '../../middlewares/sort'
|
||||||
|
@ -142,9 +142,9 @@ async function createUser (req: express.Request, res: express.Response, next: ex
|
||||||
videoQuota: body.videoQuota
|
videoQuota: body.videoQuota
|
||||||
})
|
})
|
||||||
|
|
||||||
await createUserAuthorAndChannel(user)
|
await createUserAccountAndChannel(user)
|
||||||
|
|
||||||
logger.info('User %s with its channel and author created.', body.username)
|
logger.info('User %s with its channel and account created.', body.username)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
@ -159,7 +159,7 @@ async function registerUser (req: express.Request, res: express.Response, next:
|
||||||
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
||||||
})
|
})
|
||||||
|
|
||||||
await createUserAuthorAndChannel(user)
|
await createUserAccountAndChannel(user)
|
||||||
return res.type('json').status(204).end()
|
return res.type('json').status(204).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,14 @@ import {
|
||||||
videoChannelsRemoveValidator,
|
videoChannelsRemoveValidator,
|
||||||
videoChannelGetValidator,
|
videoChannelGetValidator,
|
||||||
videoChannelsUpdateValidator,
|
videoChannelsUpdateValidator,
|
||||||
listVideoAuthorChannelsValidator,
|
listVideoAccountChannelsValidator,
|
||||||
asyncMiddleware
|
asyncMiddleware
|
||||||
} from '../../../middlewares'
|
} from '../../../middlewares'
|
||||||
import {
|
import {
|
||||||
createVideoChannel,
|
createVideoChannel,
|
||||||
updateVideoChannelToFriends
|
updateVideoChannelToFriends
|
||||||
} from '../../../lib'
|
} from '../../../lib'
|
||||||
import { VideoChannelInstance, AuthorInstance } from '../../../models'
|
import { VideoChannelInstance, AccountInstance } from '../../../models'
|
||||||
import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
|
import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
|
||||||
|
|
||||||
const videoChannelRouter = express.Router()
|
const videoChannelRouter = express.Router()
|
||||||
|
@ -37,9 +37,9 @@ videoChannelRouter.get('/channels',
|
||||||
asyncMiddleware(listVideoChannels)
|
asyncMiddleware(listVideoChannels)
|
||||||
)
|
)
|
||||||
|
|
||||||
videoChannelRouter.get('/authors/:authorId/channels',
|
videoChannelRouter.get('/accounts/:accountId/channels',
|
||||||
listVideoAuthorChannelsValidator,
|
listVideoAccountChannelsValidator,
|
||||||
asyncMiddleware(listVideoAuthorChannels)
|
asyncMiddleware(listVideoAccountChannels)
|
||||||
)
|
)
|
||||||
|
|
||||||
videoChannelRouter.post('/channels',
|
videoChannelRouter.post('/channels',
|
||||||
|
@ -79,8 +79,8 @@ async function listVideoChannels (req: express.Request, res: express.Response, n
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const resultList = await db.VideoChannel.listByAuthor(res.locals.author.id)
|
const resultList = await db.VideoChannel.listByAccount(res.locals.account.id)
|
||||||
|
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||||
}
|
}
|
||||||
|
@ -101,11 +101,11 @@ async function addVideoChannelRetryWrapper (req: express.Request, res: express.R
|
||||||
|
|
||||||
async function addVideoChannel (req: express.Request, res: express.Response) {
|
async function addVideoChannel (req: express.Request, res: express.Response) {
|
||||||
const videoChannelInfo: VideoChannelCreate = req.body
|
const videoChannelInfo: VideoChannelCreate = req.body
|
||||||
const author: AuthorInstance = res.locals.oauth.token.User.Author
|
const account: AccountInstance = res.locals.oauth.token.User.Account
|
||||||
let videoChannelCreated: VideoChannelInstance
|
let videoChannelCreated: VideoChannelInstance
|
||||||
|
|
||||||
await db.sequelize.transaction(async t => {
|
await db.sequelize.transaction(async t => {
|
||||||
videoChannelCreated = await createVideoChannel(videoChannelInfo, author, t)
|
videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t)
|
||||||
})
|
})
|
||||||
|
|
||||||
logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid)
|
logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid)
|
||||||
|
@ -179,7 +179,7 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const videoChannelWithVideos = await db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id)
|
const videoChannelWithVideos = await db.VideoChannel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
|
||||||
|
|
||||||
return res.json(videoChannelWithVideos.toFormattedJSON())
|
return res.json(videoChannelWithVideos.toFormattedJSON())
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,7 +395,7 @@ async function removeVideo (req: express.Request, res: express.Response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const resultList = await db.Video.searchAndPopulateAuthorAndPodAndTags(
|
const resultList = await db.Video.searchAndPopulateAccountAndPodAndTags(
|
||||||
req.params.value,
|
req.params.value,
|
||||||
req.query.field,
|
req.query.field,
|
||||||
req.query.start,
|
req.query.start,
|
||||||
|
|
|
@ -110,9 +110,9 @@ async function generateWatchHtmlPage (req: express.Request, res: express.Respons
|
||||||
|
|
||||||
// Let Angular application handle errors
|
// Let Angular application handle errors
|
||||||
if (validator.isUUID(videoId, 4)) {
|
if (validator.isUUID(videoId, 4)) {
|
||||||
videoPromise = db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(videoId)
|
videoPromise = db.Video.loadByUUIDAndPopulateAccountAndPodAndTags(videoId)
|
||||||
} else if (validator.isInt(videoId)) {
|
} else if (validator.isInt(videoId)) {
|
||||||
videoPromise = db.Video.loadAndPopulateAuthorAndPodAndTags(+videoId)
|
videoPromise = db.Video.loadAndPopulateAccountAndPodAndTags(+videoId)
|
||||||
} else {
|
} else {
|
||||||
return res.sendFile(indexPath)
|
return res.sendFile(indexPath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ function generateOEmbed (req: express.Request, res: express.Response, next: expr
|
||||||
width: embedWidth,
|
width: embedWidth,
|
||||||
height: embedHeight,
|
height: embedHeight,
|
||||||
title: video.name,
|
title: video.name,
|
||||||
author_name: video.VideoChannel.Author.name,
|
author_name: video.VideoChannel.Account.name,
|
||||||
provider_name: 'PeerTube',
|
provider_name: 'PeerTube',
|
||||||
provider_url: webserverUrl
|
provider_url: webserverUrl
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,6 @@ export * from './misc'
|
||||||
export * from './pods'
|
export * from './pods'
|
||||||
export * from './pods'
|
export * from './pods'
|
||||||
export * from './users'
|
export * from './users'
|
||||||
|
export * from './video-accounts'
|
||||||
export * from './video-channels'
|
export * from './video-channels'
|
||||||
export * from './videos'
|
export * from './videos'
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import * as Promise from 'bluebird'
|
||||||
|
import * as validator from 'validator'
|
||||||
|
import * as express from 'express'
|
||||||
|
import 'express-validator'
|
||||||
|
|
||||||
|
import { database as db } from '../../initializers'
|
||||||
|
import { AccountInstance } from '../../models'
|
||||||
|
import { logger } from '../logger'
|
||||||
|
|
||||||
|
import { isUserUsernameValid } from './users'
|
||||||
|
|
||||||
|
function isVideoAccountNameValid (value: string) {
|
||||||
|
return isUserUsernameValid(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) {
|
||||||
|
let promise: Promise<AccountInstance>
|
||||||
|
if (validator.isInt(id)) {
|
||||||
|
promise = db.Account.load(+id)
|
||||||
|
} else { // UUID
|
||||||
|
promise = db.Account.loadByUUID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(account => {
|
||||||
|
if (!account) {
|
||||||
|
return res.status(404)
|
||||||
|
.json({ error: 'Video account not found' })
|
||||||
|
.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.account = account
|
||||||
|
callback()
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error('Error in video account request validator.', err)
|
||||||
|
return res.sendStatus(500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
checkVideoAccountExists,
|
||||||
|
isVideoAccountNameValid
|
||||||
|
}
|
|
@ -26,9 +26,9 @@ function isVideoChannelUUIDValid (value: string) {
|
||||||
function checkVideoChannelExists (id: string, res: express.Response, callback: () => void) {
|
function checkVideoChannelExists (id: string, res: express.Response, callback: () => void) {
|
||||||
let promise: Promise<VideoChannelInstance>
|
let promise: Promise<VideoChannelInstance>
|
||||||
if (validator.isInt(id)) {
|
if (validator.isInt(id)) {
|
||||||
promise = db.VideoChannel.loadAndPopulateAuthor(+id)
|
promise = db.VideoChannel.loadAndPopulateAccount(+id)
|
||||||
} else { // UUID
|
} else { // UUID
|
||||||
promise = db.VideoChannel.loadByUUIDAndPopulateAuthor(id)
|
promise = db.VideoChannel.loadByUUIDAndPopulateAccount(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
promise.then(videoChannel => {
|
promise.then(videoChannel => {
|
||||||
|
|
|
@ -166,9 +166,9 @@ function isVideoFileInfoHashValid (value: string) {
|
||||||
function checkVideoExists (id: string, res: express.Response, callback: () => void) {
|
function checkVideoExists (id: string, res: express.Response, callback: () => void) {
|
||||||
let promise: Promise<VideoInstance>
|
let promise: Promise<VideoInstance>
|
||||||
if (validator.isInt(id)) {
|
if (validator.isInt(id)) {
|
||||||
promise = db.Video.loadAndPopulateAuthorAndPodAndTags(+id)
|
promise = db.Video.loadAndPopulateAccountAndPodAndTags(+id)
|
||||||
} else { // UUID
|
} else { // UUID
|
||||||
promise = db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(id)
|
promise = db.Video.loadByUUIDAndPopulateAccountAndPodAndTags(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
promise.then(video => {
|
promise.then(video => {
|
||||||
|
|
|
@ -29,7 +29,7 @@ const PAGINATION_COUNT_DEFAULT = 15
|
||||||
|
|
||||||
// Sortable columns per schema
|
// Sortable columns per schema
|
||||||
const SEARCHABLE_COLUMNS = {
|
const SEARCHABLE_COLUMNS = {
|
||||||
VIDEOS: [ 'name', 'magnetUri', 'host', 'author', 'tags' ]
|
VIDEOS: [ 'name', 'magnetUri', 'host', 'account', 'tags' ]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sortable columns per schema
|
// Sortable columns per schema
|
||||||
|
|
|
@ -44,10 +44,6 @@ const database: {
|
||||||
OAuthClient?: OAuthClientModel,
|
OAuthClient?: OAuthClientModel,
|
||||||
OAuthToken?: OAuthTokenModel,
|
OAuthToken?: OAuthTokenModel,
|
||||||
Pod?: PodModel,
|
Pod?: PodModel,
|
||||||
RequestToPod?: RequestToPodModel,
|
|
||||||
RequestVideoEvent?: RequestVideoEventModel,
|
|
||||||
RequestVideoQadu?: RequestVideoQaduModel,
|
|
||||||
Request?: RequestModel,
|
|
||||||
Tag?: TagModel,
|
Tag?: TagModel,
|
||||||
AccountVideoRate?: AccountVideoRateModel,
|
AccountVideoRate?: AccountVideoRateModel,
|
||||||
AccountFollow?: AccountFollowModel,
|
AccountFollow?: AccountFollowModel,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { database as db } from './database'
|
||||||
import { CONFIG, LAST_MIGRATION_VERSION, CACHE } from './constants'
|
import { CONFIG, LAST_MIGRATION_VERSION, CACHE } from './constants'
|
||||||
import { clientsExist, usersExist } from './checker'
|
import { clientsExist, usersExist } from './checker'
|
||||||
import { logger, createCertsIfNotExist, mkdirpPromise, rimrafPromise } from '../helpers'
|
import { logger, createCertsIfNotExist, mkdirpPromise, rimrafPromise } from '../helpers'
|
||||||
import { createUserAuthorAndChannel } from '../lib'
|
import { createUserAccountAndChannel } from '../lib'
|
||||||
import { UserRole } from '../../shared'
|
import { UserRole } from '../../shared'
|
||||||
|
|
||||||
async function installApplication () {
|
async function installApplication () {
|
||||||
|
@ -117,7 +117,7 @@ async function createOAuthAdminIfNotExist () {
|
||||||
}
|
}
|
||||||
const user = db.User.build(userData)
|
const user = db.User.build(userData)
|
||||||
|
|
||||||
await createUserAuthorAndChannel(user, validatePassword)
|
await createUserAccountAndChannel(user, validatePassword)
|
||||||
logger.info('Username: ' + username)
|
logger.info('Username: ' + username)
|
||||||
logger.info('User password: ' + password)
|
logger.info('User password: ' + password)
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ class VideosPreviewCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadPreviews (key: string) {
|
private async loadPreviews (key: string) {
|
||||||
const video = await db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(key)
|
const video = await db.Video.loadByUUIDAndPopulateAccountAndPodAndTags(key)
|
||||||
if (!video) return undefined
|
if (!video) return undefined
|
||||||
|
|
||||||
if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName())
|
if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName())
|
||||||
|
|
|
@ -1,567 +0,0 @@
|
||||||
import * as request from 'request'
|
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
import * as Bluebird from 'bluebird'
|
|
||||||
import { join } from 'path'
|
|
||||||
|
|
||||||
import { database as db } from '../initializers/database'
|
|
||||||
import {
|
|
||||||
API_VERSION,
|
|
||||||
CONFIG,
|
|
||||||
REQUESTS_IN_PARALLEL,
|
|
||||||
REQUEST_ENDPOINTS,
|
|
||||||
REQUEST_ENDPOINT_ACTIONS,
|
|
||||||
REMOTE_SCHEME,
|
|
||||||
STATIC_PATHS
|
|
||||||
} from '../initializers'
|
|
||||||
import {
|
|
||||||
logger,
|
|
||||||
getMyPublicCert,
|
|
||||||
makeSecureRequest,
|
|
||||||
makeRetryRequest
|
|
||||||
} from '../helpers'
|
|
||||||
import {
|
|
||||||
RequestScheduler,
|
|
||||||
RequestSchedulerOptions,
|
|
||||||
|
|
||||||
RequestVideoQaduScheduler,
|
|
||||||
RequestVideoQaduSchedulerOptions,
|
|
||||||
|
|
||||||
RequestVideoEventScheduler,
|
|
||||||
RequestVideoEventSchedulerOptions
|
|
||||||
} from './request'
|
|
||||||
import {
|
|
||||||
PodInstance,
|
|
||||||
VideoInstance
|
|
||||||
} from '../models'
|
|
||||||
import {
|
|
||||||
RequestEndpoint,
|
|
||||||
RequestVideoEventType,
|
|
||||||
RequestVideoQaduType,
|
|
||||||
RemoteVideoCreateData,
|
|
||||||
RemoteVideoUpdateData,
|
|
||||||
RemoteVideoRemoveData,
|
|
||||||
RemoteVideoReportAbuseData,
|
|
||||||
ResultList,
|
|
||||||
RemoteVideoRequestType,
|
|
||||||
Pod as FormattedPod,
|
|
||||||
RemoteVideoChannelCreateData,
|
|
||||||
RemoteVideoChannelUpdateData,
|
|
||||||
RemoteVideoChannelRemoveData,
|
|
||||||
RemoteVideoAuthorCreateData,
|
|
||||||
RemoteVideoAuthorRemoveData
|
|
||||||
} from '../../shared'
|
|
||||||
|
|
||||||
type QaduParam = { videoId: number, type: RequestVideoQaduType }
|
|
||||||
type EventParam = { videoId: number, type: RequestVideoEventType }
|
|
||||||
|
|
||||||
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
|
|
||||||
|
|
||||||
const requestScheduler = new RequestScheduler()
|
|
||||||
const requestVideoQaduScheduler = new RequestVideoQaduScheduler()
|
|
||||||
const requestVideoEventScheduler = new RequestVideoEventScheduler()
|
|
||||||
|
|
||||||
function activateSchedulers () {
|
|
||||||
requestScheduler.activate()
|
|
||||||
requestVideoQaduScheduler.activate()
|
|
||||||
requestVideoEventScheduler.activate()
|
|
||||||
}
|
|
||||||
|
|
||||||
function addVideoToFriends (videoData: RemoteVideoCreateData, transaction: Sequelize.Transaction) {
|
|
||||||
const options = {
|
|
||||||
type: ENDPOINT_ACTIONS.ADD_VIDEO,
|
|
||||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
|
||||||
data: videoData,
|
|
||||||
transaction
|
|
||||||
}
|
|
||||||
return createRequest(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateVideoToFriends (videoData: RemoteVideoUpdateData, transaction: Sequelize.Transaction) {
|
|
||||||
const options = {
|
|
||||||
type: ENDPOINT_ACTIONS.UPDATE_VIDEO,
|
|
||||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
|
||||||
data: videoData,
|
|
||||||
transaction
|
|
||||||
}
|
|
||||||
return createRequest(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeVideoToFriends (videoParams: RemoteVideoRemoveData, transaction?: Sequelize.Transaction) {
|
|
||||||
const options = {
|
|
||||||
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.VideoChannel.Author.podId ],
|
|
||||||
transaction
|
|
||||||
}
|
|
||||||
return createRequest(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function quickAndDirtyUpdateVideoToFriends (qaduParam: QaduParam, transaction?: Sequelize.Transaction) {
|
|
||||||
const options = {
|
|
||||||
videoId: qaduParam.videoId,
|
|
||||||
type: qaduParam.type,
|
|
||||||
transaction
|
|
||||||
}
|
|
||||||
return createVideoQaduRequest(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function quickAndDirtyUpdatesVideoToFriends (qadusParams: QaduParam[], transaction: Sequelize.Transaction) {
|
|
||||||
const tasks = []
|
|
||||||
|
|
||||||
qadusParams.forEach(qaduParams => {
|
|
||||||
tasks.push(quickAndDirtyUpdateVideoToFriends(qaduParams, transaction))
|
|
||||||
})
|
|
||||||
|
|
||||||
return Promise.all(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
function addEventToRemoteVideo (eventParam: EventParam, transaction?: Sequelize.Transaction) {
|
|
||||||
const options = {
|
|
||||||
videoId: eventParam.videoId,
|
|
||||||
type: eventParam.type,
|
|
||||||
transaction
|
|
||||||
}
|
|
||||||
return createVideoEventRequest(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function addEventsToRemoteVideo (eventsParams: EventParam[], transaction: Sequelize.Transaction) {
|
|
||||||
const tasks = []
|
|
||||||
|
|
||||||
for (const eventParams of eventsParams) {
|
|
||||||
tasks.push(addEventToRemoteVideo(eventParams, transaction))
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function hasFriends () {
|
|
||||||
const count = await db.Pod.countAll()
|
|
||||||
|
|
||||||
return count !== 0
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeFriends (hosts: string[]) {
|
|
||||||
const podsScore = {}
|
|
||||||
|
|
||||||
logger.info('Make friends!')
|
|
||||||
const cert = await getMyPublicCert()
|
|
||||||
|
|
||||||
for (const host of hosts) {
|
|
||||||
await computeForeignPodsList(host, podsScore)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug('Pods scores computed.', { podsScore: podsScore })
|
|
||||||
|
|
||||||
const podsList = computeWinningPods(hosts, podsScore)
|
|
||||||
logger.debug('Pods that we keep.', { podsToKeep: podsList })
|
|
||||||
|
|
||||||
return makeRequestsToWinningPods(cert, podsList)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function quitFriends () {
|
|
||||||
// Stop pool requests
|
|
||||||
requestScheduler.deactivate()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await requestScheduler.flush()
|
|
||||||
|
|
||||||
await requestVideoQaduScheduler.flush()
|
|
||||||
|
|
||||||
const pods = await db.Pod.list()
|
|
||||||
const requestParams = {
|
|
||||||
method: 'POST' as 'POST',
|
|
||||||
path: '/api/' + API_VERSION + '/remote/pods/remove',
|
|
||||||
toPod: null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Announce we quit them
|
|
||||||
// We don't care if the request fails
|
|
||||||
// The other pod will exclude us automatically after a while
|
|
||||||
try {
|
|
||||||
await Bluebird.map(pods, pod => {
|
|
||||||
requestParams.toPod = pod
|
|
||||||
|
|
||||||
return makeSecureRequest(requestParams)
|
|
||||||
}, { concurrency: REQUESTS_IN_PARALLEL })
|
|
||||||
} catch (err) { // Don't stop the process
|
|
||||||
logger.error('Some errors while quitting friends.', err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tasks = []
|
|
||||||
for (const pod of pods) {
|
|
||||||
tasks.push(pod.destroy())
|
|
||||||
}
|
|
||||||
await Promise.all(pods)
|
|
||||||
|
|
||||||
logger.info('Removed all remote videos.')
|
|
||||||
|
|
||||||
requestScheduler.activate()
|
|
||||||
} catch (err) {
|
|
||||||
// Don't forget to re activate the scheduler, even if there was an error
|
|
||||||
requestScheduler.activate()
|
|
||||||
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendOwnedDataToPod (podId: number) {
|
|
||||||
// First send authors
|
|
||||||
await sendOwnedAuthorsToPod(podId)
|
|
||||||
await sendOwnedChannelsToPod(podId)
|
|
||||||
await sendOwnedVideosToPod(podId)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendOwnedChannelsToPod (podId: number) {
|
|
||||||
const videoChannels = await db.VideoChannel.listOwned()
|
|
||||||
|
|
||||||
const tasks: Promise<any>[] = []
|
|
||||||
for (const videoChannel of videoChannels) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendOwnedAuthorsToPod (podId: number) {
|
|
||||||
const authors = await db.Author.listOwned()
|
|
||||||
const tasks: Promise<any>[] = []
|
|
||||||
|
|
||||||
for (const author of authors) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendOwnedVideosToPod (podId: number) {
|
|
||||||
const videosList = await db.Video.listOwnedAndPopulateAuthorAndTags()
|
|
||||||
const tasks: Bluebird<any>[] = []
|
|
||||||
|
|
||||||
for (const video of videosList) {
|
|
||||||
const promise = video.toAddRemoteJSON()
|
|
||||||
.then(remoteVideo => {
|
|
||||||
const options = {
|
|
||||||
type: 'add-video' as 'add-video',
|
|
||||||
endpoint: REQUEST_ENDPOINTS.VIDEOS,
|
|
||||||
data: remoteVideo,
|
|
||||||
toIds: [ podId ],
|
|
||||||
transaction: null
|
|
||||||
}
|
|
||||||
return createRequest(options)
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Cannot convert video to remote.', err)
|
|
||||||
// Don't break the process
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
tasks.push(promise)
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchRemoteDescription (video: VideoInstance) {
|
|
||||||
const host = video.VideoChannel.Author.Pod.host
|
|
||||||
const path = video.getDescriptionPath()
|
|
||||||
|
|
||||||
const requestOptions = {
|
|
||||||
url: REMOTE_SCHEME.HTTP + '://' + host + path,
|
|
||||||
json: true
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<string>((res, rej) => {
|
|
||||||
request.get(requestOptions, (err, response, body) => {
|
|
||||||
if (err) return rej(err)
|
|
||||||
|
|
||||||
return res(body.description ? body.description : '')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeFriend (pod: PodInstance) {
|
|
||||||
const requestParams = {
|
|
||||||
method: 'POST' as 'POST',
|
|
||||||
path: '/api/' + API_VERSION + '/remote/pods/remove',
|
|
||||||
toPod: pod
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await makeSecureRequest(requestParams)
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn('Cannot notify friends %s we are quitting him.', pod.host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pod.destroy()
|
|
||||||
|
|
||||||
logger.info('Removed friend %s.', pod.host)
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Cannot destroy friend %s.', pod.host, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRequestScheduler () {
|
|
||||||
return requestScheduler
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRequestVideoQaduScheduler () {
|
|
||||||
return requestVideoQaduScheduler
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRequestVideoEventScheduler () {
|
|
||||||
return requestVideoEventScheduler
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
activateSchedulers,
|
|
||||||
addVideoToFriends,
|
|
||||||
removeVideoAuthorToFriends,
|
|
||||||
updateVideoToFriends,
|
|
||||||
addVideoAuthorToFriends,
|
|
||||||
reportAbuseVideoToFriend,
|
|
||||||
quickAndDirtyUpdateVideoToFriends,
|
|
||||||
quickAndDirtyUpdatesVideoToFriends,
|
|
||||||
addEventToRemoteVideo,
|
|
||||||
addEventsToRemoteVideo,
|
|
||||||
hasFriends,
|
|
||||||
makeFriends,
|
|
||||||
quitFriends,
|
|
||||||
removeFriend,
|
|
||||||
removeVideoToFriends,
|
|
||||||
sendOwnedDataToPod,
|
|
||||||
getRequestScheduler,
|
|
||||||
getRequestVideoQaduScheduler,
|
|
||||||
getRequestVideoEventScheduler,
|
|
||||||
fetchRemotePreview,
|
|
||||||
addVideoChannelToFriends,
|
|
||||||
fetchRemoteDescription,
|
|
||||||
updateVideoChannelToFriends,
|
|
||||||
removeVideoChannelToFriends
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
async function computeForeignPodsList (host: string, podsScore: { [ host: string ]: number }) {
|
|
||||||
const result = await getForeignPodsList(host)
|
|
||||||
const foreignPodsList: { host: string }[] = result.data
|
|
||||||
|
|
||||||
// Let's give 1 point to the pod we ask the friends list
|
|
||||||
foreignPodsList.push({ host })
|
|
||||||
|
|
||||||
for (const foreignPod of foreignPodsList) {
|
|
||||||
const foreignPodHost = foreignPod.host
|
|
||||||
|
|
||||||
if (podsScore[foreignPodHost]) podsScore[foreignPodHost]++
|
|
||||||
else podsScore[foreignPodHost] = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeWinningPods (hosts: string[], podsScore: { [ host: string ]: number }) {
|
|
||||||
// Build the list of pods to add
|
|
||||||
// Only add a pod if it exists in more than a half base pods
|
|
||||||
const podsList = []
|
|
||||||
const baseScore = hosts.length / 2
|
|
||||||
|
|
||||||
for (const podHost of Object.keys(podsScore)) {
|
|
||||||
// If the pod is not me and with a good score we add it
|
|
||||||
if (isMe(podHost) === false && podsScore[podHost] > baseScore) {
|
|
||||||
podsList.push({ host: podHost })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return podsList
|
|
||||||
}
|
|
||||||
|
|
||||||
function getForeignPodsList (host: string) {
|
|
||||||
return new Promise< ResultList<FormattedPod> >((res, rej) => {
|
|
||||||
const path = '/api/' + API_VERSION + '/remote/pods/list'
|
|
||||||
|
|
||||||
request.post(REMOTE_SCHEME.HTTP + '://' + host + path, (err, response, body) => {
|
|
||||||
if (err) return rej(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const json: ResultList<FormattedPod> = JSON.parse(body)
|
|
||||||
return res(json)
|
|
||||||
} catch (err) {
|
|
||||||
return rej(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeRequestsToWinningPods (cert: string, podsList: PodInstance[]) {
|
|
||||||
// Stop pool requests
|
|
||||||
requestScheduler.deactivate()
|
|
||||||
// Flush pool requests
|
|
||||||
requestScheduler.forceSend()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Bluebird.map(podsList, async pod => {
|
|
||||||
const params = {
|
|
||||||
url: REMOTE_SCHEME.HTTP + '://' + pod.host + '/api/' + API_VERSION + '/remote/pods/add',
|
|
||||||
method: 'POST' as 'POST',
|
|
||||||
json: {
|
|
||||||
host: CONFIG.WEBSERVER.HOST,
|
|
||||||
email: CONFIG.ADMIN.EMAIL,
|
|
||||||
publicKey: cert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { response, body } = await makeRetryRequest(params)
|
|
||||||
const typedBody = body as { cert: string, email: string }
|
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
|
||||||
const podObj = db.Pod.build({ host: pod.host, publicKey: typedBody.cert, email: typedBody.email })
|
|
||||||
|
|
||||||
let podCreated: PodInstance
|
|
||||||
try {
|
|
||||||
podCreated = await podObj.save()
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Cannot add friend %s pod.', pod.host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add our videos to the request scheduler
|
|
||||||
sendOwnedDataToPod(podCreated.id)
|
|
||||||
.catch(err => logger.warn('Cannot send owned data to pod %d.', podCreated.id, err))
|
|
||||||
} else {
|
|
||||||
logger.error('Status not 200 for %s pod.', pod.host)
|
|
||||||
}
|
|
||||||
}, { concurrency: REQUESTS_IN_PARALLEL })
|
|
||||||
|
|
||||||
logger.debug('makeRequestsToWinningPods finished.')
|
|
||||||
|
|
||||||
requestScheduler.activate()
|
|
||||||
} catch (err) {
|
|
||||||
// Final callback, we've ended all the requests
|
|
||||||
// Now we made new friends, we can re activate the pool of requests
|
|
||||||
requestScheduler.activate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper that populate "toIds" argument with all our friends if it is not specified
|
|
||||||
type CreateRequestOptions = {
|
|
||||||
type: RemoteVideoRequestType
|
|
||||||
endpoint: RequestEndpoint
|
|
||||||
data: Object
|
|
||||||
toIds?: number[]
|
|
||||||
transaction: Sequelize.Transaction
|
|
||||||
}
|
|
||||||
async function createRequest (options: CreateRequestOptions) {
|
|
||||||
if (options.toIds !== undefined) {
|
|
||||||
await requestScheduler.createRequest(options as RequestSchedulerOptions)
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the "toIds" pods is not specified, we send the request to all our friends
|
|
||||||
const podIds = await db.Pod.listAllIds(options.transaction)
|
|
||||||
|
|
||||||
const newOptions = Object.assign(options, { toIds: podIds })
|
|
||||||
await requestScheduler.createRequest(newOptions)
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function createVideoQaduRequest (options: RequestVideoQaduSchedulerOptions) {
|
|
||||||
return requestVideoQaduScheduler.createRequest(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function createVideoEventRequest (options: RequestVideoEventSchedulerOptions) {
|
|
||||||
return requestVideoEventScheduler.createRequest(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMe (host: string) {
|
|
||||||
return host === CONFIG.WEBSERVER.HOST
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ import { addVideoToFriends } from '../../friends'
|
||||||
import { JobScheduler } from '../job-scheduler'
|
import { JobScheduler } from '../job-scheduler'
|
||||||
|
|
||||||
async function process (data: { videoUUID: string }, jobId: number) {
|
async function process (data: { videoUUID: string }, jobId: number) {
|
||||||
const video = await db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID)
|
const video = await db.Video.loadByUUIDAndPopulateAccountAndPodAndTags(data.videoUUID)
|
||||||
// No video, maybe deleted?
|
// No video, maybe deleted?
|
||||||
if (!video) {
|
if (!video) {
|
||||||
logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid })
|
logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid })
|
||||||
|
@ -30,7 +30,7 @@ async function onSuccess (jobId: number, video: VideoInstance) {
|
||||||
logger.info('Job %d is a success.', jobId)
|
logger.info('Job %d is a success.', jobId)
|
||||||
|
|
||||||
// Maybe the video changed in database, refresh it
|
// Maybe the video changed in database, refresh it
|
||||||
const videoDatabase = await db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(video.uuid)
|
const videoDatabase = await db.Video.loadByUUIDAndPopulateAccountAndPodAndTags(video.uuid)
|
||||||
// Video does not exist anymore
|
// Video does not exist anymore
|
||||||
if (!videoDatabase) return undefined
|
if (!videoDatabase) return undefined
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { VideoInstance } from '../../../models'
|
||||||
import { VideoResolution } from '../../../../shared'
|
import { VideoResolution } from '../../../../shared'
|
||||||
|
|
||||||
async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) {
|
async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) {
|
||||||
const video = await db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID)
|
const video = await db.Video.loadByUUIDAndPopulateAccountAndPodAndTags(data.videoUUID)
|
||||||
// No video, maybe deleted?
|
// No video, maybe deleted?
|
||||||
if (!video) {
|
if (!video) {
|
||||||
logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid })
|
logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid })
|
||||||
|
@ -28,7 +28,7 @@ async function onSuccess (jobId: number, video: VideoInstance) {
|
||||||
logger.info('Job %d is a success.', jobId)
|
logger.info('Job %d is a success.', jobId)
|
||||||
|
|
||||||
// Maybe the video changed in database, refresh it
|
// Maybe the video changed in database, refresh it
|
||||||
const videoDatabase = await db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(video.uuid)
|
const videoDatabase = await db.Video.loadByUUIDAndPopulateAccountAndPodAndTags(video.uuid)
|
||||||
// Video does not exist anymore
|
// Video does not exist anymore
|
||||||
if (!videoDatabase) return undefined
|
if (!videoDatabase) return undefined
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
|
||||||
name: videoChannelInfo.name,
|
name: videoChannelInfo.name,
|
||||||
description: videoChannelInfo.description,
|
description: videoChannelInfo.description,
|
||||||
remote: false,
|
remote: false,
|
||||||
authorId: account.id
|
accountId: account.id
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoChannel = db.VideoChannel.build(videoChannelData)
|
const videoChannel = db.VideoChannel.build(videoChannelData)
|
||||||
|
|
|
@ -9,19 +9,19 @@ import {
|
||||||
isVideoChannelDescriptionValid,
|
isVideoChannelDescriptionValid,
|
||||||
isVideoChannelNameValid,
|
isVideoChannelNameValid,
|
||||||
checkVideoChannelExists,
|
checkVideoChannelExists,
|
||||||
checkVideoAuthorExists
|
checkVideoAccountExists
|
||||||
} from '../../helpers'
|
} from '../../helpers'
|
||||||
import { UserInstance } from '../../models'
|
import { UserInstance } from '../../models'
|
||||||
import { UserRight } from '../../../shared'
|
import { UserRight } from '../../../shared'
|
||||||
|
|
||||||
const listVideoAuthorChannelsValidator = [
|
const listVideoAccountChannelsValidator = [
|
||||||
param('authorId').custom(isIdOrUUIDValid).withMessage('Should have a valid author id'),
|
param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'),
|
||||||
|
|
||||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking listVideoAuthorChannelsValidator parameters', { parameters: req.body })
|
logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body })
|
||||||
|
|
||||||
checkErrors(req, res, () => {
|
checkErrors(req, res, () => {
|
||||||
checkVideoAuthorExists(req.params.authorId, res, next)
|
checkVideoAccountExists(req.params.accountId, res, next)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -54,7 +54,7 @@ const videoChannelsUpdateValidator = [
|
||||||
.end()
|
.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.locals.videoChannel.Author.userId !== res.locals.oauth.token.User.id) {
|
if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) {
|
||||||
return res.status(403)
|
return res.status(403)
|
||||||
.json({ error: 'Cannot update video channel of another user' })
|
.json({ error: 'Cannot update video channel of another user' })
|
||||||
.end()
|
.end()
|
||||||
|
@ -98,7 +98,7 @@ const videoChannelGetValidator = [
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
listVideoAuthorChannelsValidator,
|
listVideoAccountChannelsValidator,
|
||||||
videoChannelsAddValidator,
|
videoChannelsAddValidator,
|
||||||
videoChannelsUpdateValidator,
|
videoChannelsUpdateValidator,
|
||||||
videoChannelsRemoveValidator,
|
videoChannelsRemoveValidator,
|
||||||
|
@ -119,8 +119,8 @@ function checkUserCanDeleteVideoChannel (res: express.Response, callback: () =>
|
||||||
|
|
||||||
// Check if the user can delete the video channel
|
// Check if the user can delete the video channel
|
||||||
// The user can delete it if s/he is an admin
|
// The user can delete it if s/he is an admin
|
||||||
// Or if s/he is the video channel's author
|
// Or if s/he is the video channel's account
|
||||||
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && res.locals.videoChannel.Author.userId !== user.id) {
|
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && res.locals.videoChannel.Account.userId !== user.id) {
|
||||||
return res.status(403)
|
return res.status(403)
|
||||||
.json({ error: 'Cannot remove video channel of another user' })
|
.json({ error: 'Cannot remove video channel of another user' })
|
||||||
.end()
|
.end()
|
||||||
|
@ -131,7 +131,7 @@ function checkUserCanDeleteVideoChannel (res: express.Response, callback: () =>
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkVideoChannelIsNotTheLastOne (res: express.Response, callback: () => void) {
|
function checkVideoChannelIsNotTheLastOne (res: express.Response, callback: () => void) {
|
||||||
db.VideoChannel.countByAuthor(res.locals.oauth.token.User.Author.id)
|
db.VideoChannel.countByAccount(res.locals.oauth.token.User.Account.id)
|
||||||
.then(count => {
|
.then(count => {
|
||||||
if (count <= 1) {
|
if (count <= 1) {
|
||||||
return res.status(409)
|
return res.status(409)
|
||||||
|
|
|
@ -48,11 +48,11 @@ const videosAddValidator = [
|
||||||
const videoFile: Express.Multer.File = req.files['videofile'][0]
|
const videoFile: Express.Multer.File = req.files['videofile'][0]
|
||||||
const user = res.locals.oauth.token.User
|
const user = res.locals.oauth.token.User
|
||||||
|
|
||||||
return db.VideoChannel.loadByIdAndAuthor(req.body.channelId, user.Author.id)
|
return db.VideoChannel.loadByIdAndAccount(req.body.channelId, user.Account.id)
|
||||||
.then(videoChannel => {
|
.then(videoChannel => {
|
||||||
if (!videoChannel) {
|
if (!videoChannel) {
|
||||||
res.status(400)
|
res.status(400)
|
||||||
.json({ error: 'Unknown video video channel for this author.' })
|
.json({ error: 'Unknown video video channel for this account.' })
|
||||||
.end()
|
.end()
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -131,7 +131,7 @@ const videosUpdateValidator = [
|
||||||
.end()
|
.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) {
|
if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
|
||||||
return res.status(403)
|
return res.status(403)
|
||||||
.json({ error: 'Cannot update video of another user' })
|
.json({ error: 'Cannot update video of another user' })
|
||||||
.end()
|
.end()
|
||||||
|
@ -163,7 +163,7 @@ const videosGetValidator = [
|
||||||
if (video.privacy !== VideoPrivacy.PRIVATE) return next()
|
if (video.privacy !== VideoPrivacy.PRIVATE) return next()
|
||||||
|
|
||||||
authenticate(req, res, () => {
|
authenticate(req, res, () => {
|
||||||
if (video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) {
|
if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
|
||||||
return res.status(403)
|
return res.status(403)
|
||||||
.json({ error: 'Cannot get this private video of another user' })
|
.json({ error: 'Cannot get this private video of another user' })
|
||||||
.end()
|
.end()
|
||||||
|
@ -256,10 +256,10 @@ function checkUserCanDeleteVideo (userId: number, res: express.Response, callbac
|
||||||
|
|
||||||
// Check if the user can delete the video
|
// Check if the user can delete the video
|
||||||
// The user can delete it if s/he is an admin
|
// The user can delete it if s/he is an admin
|
||||||
// Or if s/he is the video's author
|
// Or if s/he is the video's account
|
||||||
const author = res.locals.video.VideoChannel.Author
|
const account = res.locals.video.VideoChannel.Account
|
||||||
const user = res.locals.oauth.token.User
|
const user = res.locals.oauth.token.User
|
||||||
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO) === false && author.userId !== user.id) {
|
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO) === false && account.userId !== user.id) {
|
||||||
return res.status(403)
|
return res.status(403)
|
||||||
.json({ error: 'Cannot remove video of another user' })
|
.json({ error: 'Cannot remove video of another user' })
|
||||||
.end()
|
.end()
|
||||||
|
|
|
@ -164,7 +164,7 @@ toFormattedJSON = function (this: UserInstance) {
|
||||||
roleLabel: USER_ROLE_LABELS[this.role],
|
roleLabel: USER_ROLE_LABELS[this.role],
|
||||||
videoQuota: this.videoQuota,
|
videoQuota: this.videoQuota,
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
author: {
|
account: {
|
||||||
id: this.Account.id,
|
id: this.Account.id,
|
||||||
uuid: this.Account.uuid
|
uuid: this.Account.uuid
|
||||||
}
|
}
|
||||||
|
@ -295,7 +295,7 @@ function getOriginalVideoFileTotalFromUser (user: UserInstance) {
|
||||||
'(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
|
'(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
|
||||||
'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
|
'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
|
||||||
'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' +
|
'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' +
|
||||||
'INNER JOIN "Accounts" ON "VideoChannels"."authorId" = "Accounts"."id" ' +
|
'INNER JOIN "Accounts" ON "VideoChannels"."accountId" = "Accounts"."id" ' +
|
||||||
'INNER JOIN "Users" ON "Accounts"."userId" = "Users"."id" ' +
|
'INNER JOIN "Users" ON "Accounts"."userId" = "Users"."id" ' +
|
||||||
'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
|
'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,5 @@ export * from './application'
|
||||||
export * from './job'
|
export * from './job'
|
||||||
export * from './oauth'
|
export * from './oauth'
|
||||||
export * from './pod'
|
export * from './pod'
|
||||||
export * from './request'
|
|
||||||
export * from './account'
|
export * from './account'
|
||||||
export * from './video'
|
export * from './video'
|
||||||
|
|
|
@ -131,7 +131,7 @@ getByTokenAndPopulateUser = function (bearerToken: string) {
|
||||||
model: OAuthToken['sequelize'].models.User,
|
model: OAuthToken['sequelize'].models.User,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: OAuthToken['sequelize'].models.Author,
|
model: OAuthToken['sequelize'].models.Account,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -156,7 +156,7 @@ getByRefreshTokenAndPopulateUser = function (refreshToken: string) {
|
||||||
model: OAuthToken['sequelize'].models.User,
|
model: OAuthToken['sequelize'].models.User,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: OAuthToken['sequelize'].models.Author,
|
model: OAuthToken['sequelize'].models.Account,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import * as Promise from 'bluebird'
|
|
||||||
|
|
||||||
export interface AbstractRequestClass <T> {
|
|
||||||
countTotalRequests: () => Promise<number>
|
|
||||||
listWithLimitAndRandom: (limitPods: number, limitRequestsPerPod: number) => Promise<T>
|
|
||||||
removeWithEmptyTo: () => Promise<number>
|
|
||||||
removeAll: () => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AbstractRequestToPodClass {
|
|
||||||
removeByRequestIdsAndPod: (ids: number[], podId: number) => Promise<number>
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
export * from './abstract-request-interface'
|
|
||||||
export * from './request-interface'
|
|
||||||
export * from './request-to-pod-interface'
|
|
||||||
export * from './request-video-event-interface'
|
|
||||||
export * from './request-video-qadu-interface'
|
|
|
@ -1,46 +0,0 @@
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
import * as Promise from 'bluebird'
|
|
||||||
|
|
||||||
import { AbstractRequestClass } from './abstract-request-interface'
|
|
||||||
import { PodInstance, PodAttributes } from '../pod/pod-interface'
|
|
||||||
import { RequestEndpoint } from '../../../shared/models/request-scheduler.model'
|
|
||||||
|
|
||||||
export type RequestsGrouped = {
|
|
||||||
[ podId: number ]: {
|
|
||||||
request: RequestInstance,
|
|
||||||
pod: PodInstance
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace RequestMethods {
|
|
||||||
export type CountTotalRequests = () => Promise<number>
|
|
||||||
|
|
||||||
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsGrouped>
|
|
||||||
|
|
||||||
export type RemoveWithEmptyTo = () => Promise<number>
|
|
||||||
|
|
||||||
export type RemoveAll = () => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestClass extends AbstractRequestClass<RequestsGrouped> {
|
|
||||||
countTotalRequests: RequestMethods.CountTotalRequests
|
|
||||||
listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom
|
|
||||||
removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo
|
|
||||||
removeAll: RequestMethods.RemoveAll
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestAttributes {
|
|
||||||
request: object
|
|
||||||
endpoint: RequestEndpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestInstance extends RequestClass, RequestAttributes, Sequelize.Instance<RequestAttributes> {
|
|
||||||
id: number
|
|
||||||
createdAt: Date
|
|
||||||
updatedAt: Date
|
|
||||||
|
|
||||||
setPods: Sequelize.HasManySetAssociationsMixin<PodAttributes, number>
|
|
||||||
Pods: PodInstance[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestModel extends RequestClass, Sequelize.Model<RequestInstance, RequestAttributes> {}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
import * as Promise from 'bluebird'
|
|
||||||
|
|
||||||
import { AbstractRequestToPodClass } from './abstract-request-interface'
|
|
||||||
|
|
||||||
export namespace RequestToPodMethods {
|
|
||||||
export type RemoveByRequestIdsAndPod = (requestsIds: number[], podId: number) => Promise<number>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestToPodClass extends AbstractRequestToPodClass {
|
|
||||||
removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestToPodAttributes {
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestToPodInstance extends RequestToPodClass, RequestToPodAttributes, Sequelize.Instance<RequestToPodAttributes> {
|
|
||||||
id: number
|
|
||||||
createdAt: Date
|
|
||||||
updatedAt: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestToPodModel extends RequestToPodClass, Sequelize.Model<RequestToPodInstance, RequestToPodAttributes> {}
|
|
|
@ -1,51 +0,0 @@
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
|
|
||||||
import { addMethodsToModel } from '../utils'
|
|
||||||
import {
|
|
||||||
RequestToPodInstance,
|
|
||||||
RequestToPodAttributes,
|
|
||||||
|
|
||||||
RequestToPodMethods
|
|
||||||
} from './request-to-pod-interface'
|
|
||||||
|
|
||||||
let RequestToPod: Sequelize.Model<RequestToPodInstance, RequestToPodAttributes>
|
|
||||||
let removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod
|
|
||||||
|
|
||||||
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
|
||||||
RequestToPod = sequelize.define<RequestToPodInstance, RequestToPodAttributes>('RequestToPod', {}, {
|
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
fields: [ 'requestId' ]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fields: [ 'podId' ]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fields: [ 'requestId', 'podId' ],
|
|
||||||
unique: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const classMethods = [
|
|
||||||
removeByRequestIdsAndPod
|
|
||||||
]
|
|
||||||
addMethodsToModel(RequestToPod, classMethods)
|
|
||||||
|
|
||||||
return RequestToPod
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
removeByRequestIdsAndPod = function (requestsIds: number[], podId: number) {
|
|
||||||
const query = {
|
|
||||||
where: {
|
|
||||||
requestId: {
|
|
||||||
[Sequelize.Op.in]: requestsIds
|
|
||||||
},
|
|
||||||
podId: podId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return RequestToPod.destroy(query)
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
import * as Promise from 'bluebird'
|
|
||||||
|
|
||||||
import { AbstractRequestClass, AbstractRequestToPodClass } from './abstract-request-interface'
|
|
||||||
import { VideoInstance } from '../video/video-interface'
|
|
||||||
import { PodInstance } from '../pod/pod-interface'
|
|
||||||
|
|
||||||
import { RequestVideoEventType } from '../../../shared/models/request-scheduler.model'
|
|
||||||
|
|
||||||
export type RequestsVideoEventGrouped = {
|
|
||||||
[ podId: number ]: {
|
|
||||||
id: number
|
|
||||||
type: RequestVideoEventType
|
|
||||||
count: number
|
|
||||||
video: VideoInstance
|
|
||||||
pod: PodInstance
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace RequestVideoEventMethods {
|
|
||||||
export type CountTotalRequests = () => Promise<number>
|
|
||||||
|
|
||||||
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsVideoEventGrouped>
|
|
||||||
|
|
||||||
export type RemoveByRequestIdsAndPod = (ids: number[], podId: number) => Promise<number>
|
|
||||||
|
|
||||||
export type RemoveAll = () => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestVideoEventClass extends AbstractRequestClass<RequestsVideoEventGrouped>, AbstractRequestToPodClass {
|
|
||||||
countTotalRequests: RequestVideoEventMethods.CountTotalRequests
|
|
||||||
listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom
|
|
||||||
removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod
|
|
||||||
removeAll: RequestVideoEventMethods.RemoveAll
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestVideoEventAttributes {
|
|
||||||
type: RequestVideoEventType
|
|
||||||
count: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestVideoEventInstance
|
|
||||||
extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> {
|
|
||||||
id: number
|
|
||||||
|
|
||||||
Video: VideoInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestVideoEventModel
|
|
||||||
extends RequestVideoEventClass, Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes> {}
|
|
|
@ -1,191 +0,0 @@
|
||||||
/*
|
|
||||||
Request Video events (likes, dislikes, views...)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { values } from 'lodash'
|
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
|
|
||||||
import { database as db } from '../../initializers/database'
|
|
||||||
import { REQUEST_VIDEO_EVENT_TYPES } from '../../initializers'
|
|
||||||
import { isVideoEventCountValid } from '../../helpers'
|
|
||||||
import { addMethodsToModel } from '../utils'
|
|
||||||
import {
|
|
||||||
RequestVideoEventInstance,
|
|
||||||
RequestVideoEventAttributes,
|
|
||||||
|
|
||||||
RequestVideoEventMethods,
|
|
||||||
RequestsVideoEventGrouped
|
|
||||||
} from './request-video-event-interface'
|
|
||||||
|
|
||||||
let RequestVideoEvent: Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes>
|
|
||||||
let countTotalRequests: RequestVideoEventMethods.CountTotalRequests
|
|
||||||
let listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom
|
|
||||||
let removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod
|
|
||||||
let removeAll: RequestVideoEventMethods.RemoveAll
|
|
||||||
|
|
||||||
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
|
||||||
RequestVideoEvent = sequelize.define<RequestVideoEventInstance, RequestVideoEventAttributes>('RequestVideoEvent',
|
|
||||||
{
|
|
||||||
type: {
|
|
||||||
type: DataTypes.ENUM(values(REQUEST_VIDEO_EVENT_TYPES)),
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
count: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
allowNull: false,
|
|
||||||
validate: {
|
|
||||||
countValid: function (value) {
|
|
||||||
const res = isVideoEventCountValid(value)
|
|
||||||
if (res === false) throw new Error('Video event count is not valid.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
updatedAt: false,
|
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
fields: [ 'videoId' ]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const classMethods = [
|
|
||||||
associate,
|
|
||||||
|
|
||||||
listWithLimitAndRandom,
|
|
||||||
countTotalRequests,
|
|
||||||
removeAll,
|
|
||||||
removeByRequestIdsAndPod
|
|
||||||
]
|
|
||||||
addMethodsToModel(RequestVideoEvent, classMethods)
|
|
||||||
|
|
||||||
return RequestVideoEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------ STATICS ------------------------------
|
|
||||||
|
|
||||||
function associate (models) {
|
|
||||||
RequestVideoEvent.belongsTo(models.Video, {
|
|
||||||
foreignKey: {
|
|
||||||
name: 'videoId',
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
onDelete: 'CASCADE'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
countTotalRequests = function () {
|
|
||||||
const query = {}
|
|
||||||
return RequestVideoEvent.count(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number) {
|
|
||||||
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 "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 => {
|
|
||||||
// We don't have friends that have requests
|
|
||||||
if (podIds.length === 0) return []
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
order: [
|
|
||||||
[ 'id', 'ASC' ]
|
|
||||||
],
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: RequestVideoEvent['sequelize'].models.Video,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: RequestVideoEvent['sequelize'].models.VideoChannel,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: RequestVideoEvent['sequelize'].models.Author,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: RequestVideoEvent['sequelize'].models.Pod,
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
[Sequelize.Op.in]: podIds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return RequestVideoEvent.findAll(query).then(requests => {
|
|
||||||
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
|
|
||||||
return requestsGrouped
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
removeByRequestIdsAndPod = function (ids: number[], podId: number) {
|
|
||||||
const query = {
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
[Sequelize.Op.in]: ids
|
|
||||||
}
|
|
||||||
},
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: RequestVideoEvent['sequelize'].models.Video,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: RequestVideoEvent['sequelize'].models.VideoChannel,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: RequestVideoEvent['sequelize'].models.Author,
|
|
||||||
where: {
|
|
||||||
podId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return RequestVideoEvent.destroy(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAll = function () {
|
|
||||||
// Delete all requests
|
|
||||||
return RequestVideoEvent.truncate({ cascade: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function groupAndTruncateRequests (events: RequestVideoEventInstance[], limitRequestsPerPod: number) {
|
|
||||||
const eventsGrouped: RequestsVideoEventGrouped = {}
|
|
||||||
|
|
||||||
events.forEach(event => {
|
|
||||||
const pod = event.Video.VideoChannel.Author.Pod
|
|
||||||
|
|
||||||
if (!eventsGrouped[pod.id]) eventsGrouped[pod.id] = []
|
|
||||||
|
|
||||||
if (eventsGrouped[pod.id].length < limitRequestsPerPod) {
|
|
||||||
eventsGrouped[pod.id].push({
|
|
||||||
id: event.id,
|
|
||||||
type: event.type,
|
|
||||||
count: event.count,
|
|
||||||
video: event.Video,
|
|
||||||
pod
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return eventsGrouped
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
import * as Promise from 'bluebird'
|
|
||||||
|
|
||||||
import { AbstractRequestClass, AbstractRequestToPodClass } from './abstract-request-interface'
|
|
||||||
import { VideoInstance } from '../video/video-interface'
|
|
||||||
import { PodInstance } from '../pod/pod-interface'
|
|
||||||
|
|
||||||
import { RequestVideoQaduType } from '../../../shared/models/request-scheduler.model'
|
|
||||||
|
|
||||||
export type RequestsVideoQaduGrouped = {
|
|
||||||
[ podId: number ]: {
|
|
||||||
request: RequestVideoQaduInstance
|
|
||||||
video: VideoInstance
|
|
||||||
pod: PodInstance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace RequestVideoQaduMethods {
|
|
||||||
export type CountTotalRequests = () => Promise<number>
|
|
||||||
|
|
||||||
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsVideoQaduGrouped>
|
|
||||||
|
|
||||||
export type RemoveByRequestIdsAndPod = (ids: number[], podId: number) => Promise<number>
|
|
||||||
|
|
||||||
export type RemoveAll = () => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestVideoQaduClass extends AbstractRequestClass<RequestsVideoQaduGrouped>, AbstractRequestToPodClass {
|
|
||||||
countTotalRequests: RequestVideoQaduMethods.CountTotalRequests
|
|
||||||
listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom
|
|
||||||
removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod
|
|
||||||
removeAll: RequestVideoQaduMethods.RemoveAll
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestVideoQaduAttributes {
|
|
||||||
type: RequestVideoQaduType
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestVideoQaduInstance
|
|
||||||
extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> {
|
|
||||||
id: number
|
|
||||||
|
|
||||||
Pod: PodInstance
|
|
||||||
Video: VideoInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestVideoQaduModel
|
|
||||||
extends RequestVideoQaduClass, Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes> {}
|
|
|
@ -1,159 +0,0 @@
|
||||||
/*
|
|
||||||
Request Video for Quick And Dirty Updates like:
|
|
||||||
- views
|
|
||||||
- likes
|
|
||||||
- dislikes
|
|
||||||
|
|
||||||
We can't put it in the same system than basic requests for efficiency.
|
|
||||||
Moreover we don't want to slow down the basic requests with a lot of views/likes/dislikes requests.
|
|
||||||
So we put it an independant request scheduler.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { values } from 'lodash'
|
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
|
|
||||||
import { database as db } from '../../initializers/database'
|
|
||||||
import { REQUEST_VIDEO_QADU_TYPES } from '../../initializers'
|
|
||||||
import { addMethodsToModel } from '../utils'
|
|
||||||
import {
|
|
||||||
RequestVideoQaduInstance,
|
|
||||||
RequestVideoQaduAttributes,
|
|
||||||
|
|
||||||
RequestVideoQaduMethods
|
|
||||||
} from './request-video-qadu-interface'
|
|
||||||
|
|
||||||
let RequestVideoQadu: Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes>
|
|
||||||
let countTotalRequests: RequestVideoQaduMethods.CountTotalRequests
|
|
||||||
let listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom
|
|
||||||
let removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod
|
|
||||||
let removeAll: RequestVideoQaduMethods.RemoveAll
|
|
||||||
|
|
||||||
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
|
||||||
RequestVideoQadu = sequelize.define<RequestVideoQaduInstance, RequestVideoQaduAttributes>('RequestVideoQadu',
|
|
||||||
{
|
|
||||||
type: {
|
|
||||||
type: DataTypes.ENUM(values(REQUEST_VIDEO_QADU_TYPES)),
|
|
||||||
allowNull: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timestamps: false,
|
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
fields: [ 'podId' ]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fields: [ 'videoId' ]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const classMethods = [
|
|
||||||
associate,
|
|
||||||
|
|
||||||
listWithLimitAndRandom,
|
|
||||||
countTotalRequests,
|
|
||||||
removeAll,
|
|
||||||
removeByRequestIdsAndPod
|
|
||||||
]
|
|
||||||
addMethodsToModel(RequestVideoQadu, classMethods)
|
|
||||||
|
|
||||||
return RequestVideoQadu
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------ STATICS ------------------------------
|
|
||||||
|
|
||||||
function associate (models) {
|
|
||||||
RequestVideoQadu.belongsTo(models.Pod, {
|
|
||||||
foreignKey: {
|
|
||||||
name: 'podId',
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
onDelete: 'CASCADE'
|
|
||||||
})
|
|
||||||
|
|
||||||
RequestVideoQadu.belongsTo(models.Video, {
|
|
||||||
foreignKey: {
|
|
||||||
name: 'videoId',
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
onDelete: 'CASCADE'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
countTotalRequests = function () {
|
|
||||||
const query = {}
|
|
||||||
return RequestVideoQadu.count(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number) {
|
|
||||||
const Pod = db.Pod
|
|
||||||
const tableJoin = ''
|
|
||||||
|
|
||||||
return Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin).then(podIds => {
|
|
||||||
// We don't have friends that have requests
|
|
||||||
if (podIds.length === 0) return []
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: RequestVideoQadu['sequelize'].models.Pod,
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
[Sequelize.Op.in]: podIds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: RequestVideoQadu['sequelize'].models.Video
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return RequestVideoQadu.findAll(query).then(requests => {
|
|
||||||
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
|
|
||||||
return requestsGrouped
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
removeByRequestIdsAndPod = function (ids: number[], podId: number) {
|
|
||||||
const query = {
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
[Sequelize.Op.in]: ids
|
|
||||||
},
|
|
||||||
podId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return RequestVideoQadu.destroy(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAll = function () {
|
|
||||||
// Delete all requests
|
|
||||||
return RequestVideoQadu.truncate({ cascade: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function groupAndTruncateRequests (requests: RequestVideoQaduInstance[], limitRequestsPerPod: number) {
|
|
||||||
const requestsGrouped = {}
|
|
||||||
|
|
||||||
requests.forEach(request => {
|
|
||||||
const pod = request.Pod
|
|
||||||
|
|
||||||
if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = []
|
|
||||||
|
|
||||||
if (requestsGrouped[pod.id].length < limitRequestsPerPod) {
|
|
||||||
requestsGrouped[pod.id].push({
|
|
||||||
request: request,
|
|
||||||
video: request.Video,
|
|
||||||
pod
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return requestsGrouped
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
import { values } from 'lodash'
|
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
|
|
||||||
import { database as db } from '../../initializers/database'
|
|
||||||
import { REQUEST_ENDPOINTS } from '../../initializers'
|
|
||||||
import { addMethodsToModel } from '../utils'
|
|
||||||
import {
|
|
||||||
RequestInstance,
|
|
||||||
RequestAttributes,
|
|
||||||
|
|
||||||
RequestMethods,
|
|
||||||
RequestsGrouped
|
|
||||||
} from './request-interface'
|
|
||||||
|
|
||||||
let Request: Sequelize.Model<RequestInstance, RequestAttributes>
|
|
||||||
let countTotalRequests: RequestMethods.CountTotalRequests
|
|
||||||
let listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom
|
|
||||||
let removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo
|
|
||||||
let removeAll: RequestMethods.RemoveAll
|
|
||||||
|
|
||||||
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
|
||||||
Request = sequelize.define<RequestInstance, RequestAttributes>('Request',
|
|
||||||
{
|
|
||||||
request: {
|
|
||||||
type: DataTypes.JSON,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
endpoint: {
|
|
||||||
type: DataTypes.ENUM(values(REQUEST_ENDPOINTS)),
|
|
||||||
allowNull: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const classMethods = [
|
|
||||||
associate,
|
|
||||||
|
|
||||||
listWithLimitAndRandom,
|
|
||||||
|
|
||||||
countTotalRequests,
|
|
||||||
removeAll,
|
|
||||||
removeWithEmptyTo
|
|
||||||
]
|
|
||||||
addMethodsToModel(Request, classMethods)
|
|
||||||
|
|
||||||
return Request
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------ STATICS ------------------------------
|
|
||||||
|
|
||||||
function associate (models) {
|
|
||||||
Request.belongsToMany(models.Pod, {
|
|
||||||
foreignKey: {
|
|
||||||
name: 'requestId',
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
through: models.RequestToPod,
|
|
||||||
onDelete: 'CASCADE'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
countTotalRequests = function () {
|
|
||||||
// We need to include Pod because there are no cascade delete when a pod is removed
|
|
||||||
// So we could count requests that do not have existing pod anymore
|
|
||||||
const query = {
|
|
||||||
include: [ Request['sequelize'].models.Pod ]
|
|
||||||
}
|
|
||||||
|
|
||||||
return Request.count(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number) {
|
|
||||||
const Pod = db.Pod
|
|
||||||
const tableJoin = ''
|
|
||||||
|
|
||||||
return Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', tableJoin).then(podIds => {
|
|
||||||
// We don't have friends that have requests
|
|
||||||
if (podIds.length === 0) return []
|
|
||||||
|
|
||||||
// The first x requests of these pods
|
|
||||||
// It is very important to sort by id ASC to keep the requests order!
|
|
||||||
const query = {
|
|
||||||
order: [
|
|
||||||
[ 'id', 'ASC' ]
|
|
||||||
],
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Request['sequelize'].models.Pod,
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
[Sequelize.Op.in]: podIds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return Request.findAll(query).then(requests => {
|
|
||||||
|
|
||||||
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
|
|
||||||
return requestsGrouped
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAll = function () {
|
|
||||||
// Delete all requests
|
|
||||||
return Request.truncate({ cascade: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
removeWithEmptyTo = function () {
|
|
||||||
const query = {
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
[Sequelize.Op.notIn]: [
|
|
||||||
Sequelize.literal('SELECT "requestId" FROM "RequestToPods"')
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Request.destroy(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function groupAndTruncateRequests (requests: RequestInstance[], limitRequestsPerPod: number) {
|
|
||||||
const requestsGrouped: RequestsGrouped = {}
|
|
||||||
|
|
||||||
requests.forEach(request => {
|
|
||||||
request.Pods.forEach(pod => {
|
|
||||||
if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = []
|
|
||||||
|
|
||||||
if (requestsGrouped[pod.id].length < limitRequestsPerPod) {
|
|
||||||
requestsGrouped[pod.id].push({
|
|
||||||
request,
|
|
||||||
pod
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return requestsGrouped
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from './author-interface'
|
|
||||||
export * from './tag-interface'
|
export * from './tag-interface'
|
||||||
export * from './video-abuse-interface'
|
export * from './video-abuse-interface'
|
||||||
export * from './video-blacklist-interface'
|
export * from './video-blacklist-interface'
|
||||||
|
|
Loading…
Reference in New Issue